vCloud Automation Center Aria Automation

Email Customization in vRA

 

Introduction

  • Are you tired of seeing colorless vRA emails ?
  • Do you want to add company branding/header to the emails?
  • Do you want to add custom properties to your approval email ?
  • Do you want to customize emails specific to a particular locale/tenant ?

If answer of any of these questions is “Yes”, then continue reading this blog. This blog discusses email customization in vRA. The following two images show comparison between what default vRA email looks like and an example of how it could be possibly customized.

Out of the box Approval Email and A customized Approval Email
Out of the box Approval Email and A customized Approval Email

 

Out of the box Resource Activation Email and A customized Resource Activation Email
Out of the box Resource Activation Email and A customized Resource Activation Email

 

Email Configuration in vRA

  • As a system admin or tenant admin, I can define two email server configurations: One for inbound email server and another for outbound email server.

The outbound email server configuration is used for sending out emails, for example: request submission email, approval required email etc. The inbound email server configuration is used as replyTo email links in action based emails for example: approval emails. If inbound email server is not defined, the approver will not see “Approve”/”Reject” links in his email.

The following screenshots show how inbound and outbound email configurations look like:

Inbound Email Server Configuration
Inbound Email Server Configuration

 

Outbound Email Server Configuration
Outbound Email Server Configuration

 

  • As a tenant admin, I can also enable/disable events for which users in my tenant should receive notifications. These events are called notification scenarios. These notification scenarios can be enabled/disabled using following screen:
Configure Notification Scenarios
Configure Notification Scenarios
  • As a consumer, I can enable/disable to receive notifications. Moreover, I can also choose the locale for my emails. This is shown in the following screenshot:
User Preferences
User Preferences

 

How to Customize Emails

vRA supports email customization from 6.1 onwards. This blog is applicable only for 7.x onwards. For earlier versions, have a look at KB articles: 2088805 and 2102019. These email templates are designed using apache-velocity templates.

How to install these templates?

  • Download appropriate tar:
  • Copy tar to vRA VA
    • Windows: Use a program such as WinSCP to copy the vra_*.tar.gz to the vRA VA
    • Linux: Run following command: scp vra_7.tar.gz root@vRA VA:/
  • Run following commands on vRA VA:
    ssh root@vCAC VA
    cd /
    tar -xvzf vra_7.tar.gz
    find /vcac -type d -exec chmod o+rx {} \;
    find /vcac -type f -exec chmod o+r {} \;
  • Restart VMware vRealize Automation by running this command:
    service vcac-server restart

How templates are organized ?

There are two main folders in the templates:

  1. core – It contains the main layout of the email along with styles to be used in emails.
  2. extensions – It contains template files specific to each notification scenario. The output generated using scenario specific template file is the value of $body in the core/main.vm file.

core/main.vm

## Explains what each variable or import means in the file.

<html>
<head>
   ## Include styles for emails.
   #parse( 'core/styles.vm' ) 
</head> 
<body>
   ## Include header. Header could be defined in header.vm. For example: company logo etc
   #parse( 'core/header.vm' ) 

   <div class="main">
      ## This is the core part of the email. For each scenario, extensions/scenarioId.vm file's content is displayed here.
      $body 
   </div>

   ## Include links. This is relevant to for example: approval emails/reclamation emails etc
   #parse( 'core/links.vm' ) 

   ## Include footer. Footer could be defined in footer.vm. For example: company disclaimer
   #parse( 'core/footer.vm' ) 
</body>
</html>

## Macro that may be used by templates to render table row.
#macro (renderRow $label $val $val2) 
   #if (!$val2)
      #set ( $val2 = "" )
   #end

   #notEmpty($val)
      <tr class="layoutRow">
         <td class="layoutField layoutCell">
            <div class="label">#msg($label)</div>
         </td>
         <td class="layoutField layoutCell">
            <div class="value">$val $val2</div>
         </td>
      </tr>
   #end
#end

## Macro that may be used by templates to render table row.
#macro (renderCustomRow $label $val) 
   #if (!$val2)
      #set ( $val2 = "" )
   #end

   #notEmpty($val)
      <tr class="layoutRow">
         <td class="layoutField layoutCell">
            <div class="label">$label</div>
         </td>
         <td class="layoutField layoutCell">
            <div class="value">$val $val2</div>
         </td>
      </tr>
   #end
#end

## Macro that can be used to see all available data that can be put in any email.
#macro (showData) 
   <table border=1>
      #foreach( $key in $formData.keySet() )
         <tr>
            <td>$key</td>
            <td>$!formData.get($key)</td>
         </tr>
      #end
   </table>
#end

extensions

As mentioned in the previous section, the core part of the email i.e. $body is generated using extensions/scenarioId.vm file. There is a file exists corresponding to each scenario in extensions folder. So in order to modify email content for any scenario, its extensions/scenarioId.vm file needs to be modified.

In vRA, emails can be broadly classified in three categories:

  1. Email that contains request data (request submission email, approval email)
  2. Email that contains request data as well approval data (request completion email, request approved email, request rejected email)
  3. Email that contains resource data (Resource activation email, lease modified email)

There are three files in the extensions folder that represent this classification –
1. For request data – displayRequest
2. For request and approval data – displayApproval
3. For resource data – displayResource

For ease of code maintenance, all the emails are created using these three files. Now, let’s have a look at couple of examples.

Example – Customize Destroy Deployment Approval Emails

Out of the box destroy deployment approval email looks as follows:

Deployment Approval Email
Deployment Approval Email

 

Now, let’s customize this email as follows :

  1. If deployment’s category is “Production” say “This is a production deployment !”
  2. If deployment’s category is not “Production” say “This is NOT a production deployment”. The category is defined using a custom property – “Category”.

In order to see what data can be put in the email, add #showData() in core/main.vm template as follows:

     #parse( 'core/links.vm' )
     #parse( 'core/footer.vm' )
     #showData()
  </body> 
</html>

Now, the email would look as follows. This macro is supposed to be used at the time of customization only.

 

Available data for Deployment Approval Email
Available data for Deployment Approval Email

 

In order to modify Destroy Deployment Request Approval email, have a look at extensions/com.vmware.csp.core.approval.workitem.request.vm. The “com.vmware.csp.core.approval.workitem.request” is the id for approval notification scenario.

#set ( $keyPrefix = "source-source-" )
#parse( 'extensions/displayRequest.vm' )

Apart from defining a variable keyPrefix, it includes displayRequest.vm. Let’s have a look at this file.

#if( !$keyPrefix )
   #set ($keyPrefix = "")
#end
  
#set ($resourceAction_Name = "#valueOf('resourceAction')" )
#set( $curKey = "${keyPrefix}resourceAction" )
#set ($resourceAction = $formData.get($curKey) )
  
#if( !$resourceAction || "$!resourceAction" == "" )
   #parse( 'extensions/request_catalogItem.vm' )
#else
   #parse( 'extensions/request_resourceAction.vm' )
#end
 
#parse ( 'extensions/url_details.vm' )

This template includes specific template files for catalog item request (request_catalogItem.vm) and resource action request (request_resourceAction.vm). Let’s have a look at request_resourceAction.vm. “##” defines documentation.

## Resource Action Request Information Begins
<h2>#msg("notification.email.extensions.request.info") </h2>
<br/>

## Content Specific to Scale Out Action
#parse( 'extensions/request_resourceAction_scaleOutAction.vm' )                                

## Content Specific to Scale In Action
#parse( 'extensions/request_resourceAction_scaleInAction.vm' )                                 

<table class="sectionGrid">
   #renderRow("notification.email.extensions.action", $resourceAction_Name)
   #renderRow("notification.email.extensions.request.requestedBy", "#valueOf('requestedBy')")
   #renderRow("notification.email.extensions.request.requestDate", "#valueOf('requestedDate')")
   #renderRow("notification.email.extensions.description", "#valueOf('description')")
   #renderRow("notification.email.extensions.request.reason", "#valueOf('reasons')")
   
   ## Content Specific to Reconfigure Action
   #parse( 'extensions/request_resourceAction_reconfigure.vm' )                                     
   ## Content Specific to Change Lease Action
   #parse( 'extensions/request_resourceAction_changeLease.vm' )                              
</table><br/> 
## Resource Action Request Information Ends
 
## Resource Information Begins
<h2>#msg('notification.email.extensions.resource.information')</h2>
#set( $resource_lease = "#valueOf('resource-Lease')" )
#if("$!resource_lease" == "" || "$!resource_lease.trim()" == "" ) 
   #set( $resource_lease = "#msg('notification.email.extensions.request.unlimited')" )
#end

<table class="sectionGrid">
   #renderRow("notification.email.extensions.name", "<b>#valueOf('resource-Name')</b>")
   #renderRow("notification.email.extensions.type", "#valueOf('resource-Type')")
   #renderRow("notification.email.extensions.description", "#valueOf('resource-Description')")
   #renderRow("notification.email.extensions.component.parentComponent",  "#valueOf('resource-Parent')")
   
   ## Software fields
   #renderRow("notification.email.extensions.component.installPath", "#valueOf('resource-Software-Install-Path')")
   #renderRow("notification.email.extensions.component.groupLicense", "#valueOf('resource-Software-Group-License')")
   
   ## VM fields
   #renderRow("notification.email.extensions.interface.type", "#valueOf('resource-MachineInterfaceDisplayName')")
   #renderRow("notification.email.extensions.cpus", "#valueOf('resource-MachineCPU')")
   #renderRow("notification.email.extensions.memory", "#valueOf('resource-MachineMemory')", "MB")
   #renderRow("notification.email.extensions.storage", "#valueOf('resource-MachineStorage')", "GB")
   #renderRow("notification.email.extensions.created.on", "#valueOf('resource-DateCreated')")
   #renderRow("notification.email.extensions.component.lease", "$resource_lease") 
   #renderRow("notification.email.extensions.archive.days", "#valueOf('resource-ArchiveDays')")
     
   <br/>
   ## Disk info
   #set( $curKey = "${keyPrefix}provider-resource-DISK_VOLUMES" )
   #set( $disks = $formData.get("$curKey") )
   #if( $disks && $disks.getValue() && $disks.getValue().size() > 0 )
      #foreach( $disk in $disks.getValue() )
         #if( $disk && $disk.getValue() )
            <tr class="layoutRow">
               <td class="layoutField  layoutCell"  colspan="2">
                  <div class="label">Disk Volume #if ($disks.getValue().size()>1) - $velocityCount #end</div>
               </td>
            </tr>
            #renderRow("notification.email.extensions.disk.label", $!disk.getValue().get('DISK_LABEL'))
            #renderRow("notification.email.extensions.disk.inputId", $!disk.getValue().get('DISK_INPUT_ID'))
            #renderRow("notification.email.extensions.disk.drive", $!disk.getValue().get('DISK_DRIVE'))
            #renderRow("notification.email.extensions.disk.capacity", $!disk.getValue().get('DISK_CAPACITY'), "GB")
         #end ## end of if disk
      #end ## end of for
   #end
   #set( $disks = "" )
  
   ## Network info
   #set( $curKey = "${keyPrefix}provider-resource-NETWORK_LIST" )
   #set( $networks = $formData.get("$curKey") )
   #if( $networks && $networks.getValue() && $networks.getValue().size() > 0 )
      #foreach( $network in $networks.getValue() )
         #if( $network && $network.getValue() )
            <tr class="layoutRow">
               <td class="layoutField  layoutCell" colspan="2">
                  <div class="label">Network #if ($networks.getValue().size()>1) - $velocityCount #end </div>
               </td>
            </tr>
            #renderRow("notification.email.extensions.network.name", $!network.getValue().get("NETWORK_NAME"))
            #renderRow("notification.email.extensions.network.mac", $!network.getValue().get("NETWORK_MAC_ADDRESS"))
            #renderRow("notification.email.extensions.network.ip", $!network.getValue().get("NETWORK_ADDRESS"))
         #end ## end of if network
      #end ## end of for loop
   #end
   #set( $networks = "" ) 
     
</table>
<br/>
## Resource Information Ends

As we can see that the default page is made up of two components: Request Information and Information about the Resource on which action is requested. Similar to scale in or scale out action, another template file could be added for destroy specific code.
Lets add following line to the top of the file.

#parse( 'extensions/request_resourceAction_destroy.vm' )

Now, create a separate file (request_resourceAction_destroy.vm) for destroy deployment. The content will look as follows:

#if ($resourceAction != "{com.vmware.csp.component.iaas.proxy.provider@resource.action.name.virtual.Destroy}" && $resourceAction != "{com.vmware.csp.component.cafe.composition@resource.action.deployment.destroy.name}")
   #break
#end   
 
#set ( $destroy_category = "#valueOf('resource-PROP_Category')" )
#if ($destroy_category == "Production")
   <h2><font color="red"><b>This is a production deployment !</b></font></h2>
#else
   <h2><b>This is NOT a Production deployment. The category is "$destroy_category"</b></h2>
#end
<br/>

The changes made in any template file would be reflected in 120 sec. Also, make sure you have set right permissions to this newly created template.
Now, submit the request for destroy from a Production Category deployment and another request from Staging Category deployment. The emails would look as follows:

Approval Email for Destroy Production Deployment
Approval Email for Destroy Production Deployment

 

Destroy Non-Prod Deployment Approval
Approval Email for Destroy Non Production Deployment

 

Example – Customize Catalog Item Request Approval Email

Out of the box catalog item request approval email looks as follows:

Catalog Item Request Approval Email
Catalog Item Request Approval Email

 

Now, let’s customize this email as follows:

  1. The requestor provides “DataCenter” in the request form as one of the custom property for every machine. The approver should see this custom property in the email as he needs it to make approval decision.
Again, the data available to this email could be seen by adding #showData() in main.vm. This looks as follows:
Available data for Catalog Item Request Email
Available data for Catalog Item Request Email

As mentioned in earlier example, the template file for approval scenario includes displayRequest.vm. This further includes request_catalogItem.vm for catalog item request and request_resourceAction.vm for resource action request. Let’s have a look at request_catalogItem.vm:

## Specific code for lease calculation, don't change this.
#set( $request_leasePeriod = "#valueOf('requestLeasePeriod')" )
#if( "$!request_leasePeriod" == "" || "$!request_leasePeriod.trim()" == "" ) 
   #set( $requestIsQuoteProvided = $formData.get("${keyPrefix}requestIsQuoteProvided") )
   #if( "$!requestIsQuoteProvided" != "" && $requestIsQuoteProvided ) 
      #set( $request_leasePeriod = "#msg('notification.email.extensions.request.unlimited')" )
   #end
#end

## Show Request Information
<h2>#msg("notification.email.extensions.request.info") </h2>
<br/>
<table class="sectionGrid">
   #renderRow("notification.email.extensions.deployment", "#valueOf('catalogItem-Name')")
   #renderRow("notification.email.extensions.request.requestedBy", "#valueOf('requestedBy')")
   #renderRow("notification.email.extensions.request.requestDate", "#valueOf('requestedDate')")
   #renderRow("notification.email.extensions.description", "#valueOf('description')")
   #renderRow("notification.email.extensions.request.reason", "#valueOf('reasons')")
   #renderRow("notification.email.extensions.request.numberOfInstances", "#valueOf('_number_of_instances')")
   #renderRow("notification.email.extensions.request.leaseCost", "#valueOf('requestLeaseCost')")
   #renderRow("notification.email.extensions.request.leasePeriod", $request_leasePeriod)
   #renderRow("notification.email.extensions.request.totalCost", "#valueOf('requestTotalLeaseCost')")
</table><br/>  
## Show Request Information Ends
 
## Show Request Components
#set( $component_size = "#valueOf('component-ComponentNos') " )
#set( $Integer = 0 )
#if( "$!component_size" != "" && "$!component_size.trim()" != "" )
   #set( $componentSize = $Integer.parseInt($component_size.trim()) )
   #if( $componentSize != 0 )
      <h2>#msg("notification.email.extensions.component.componentInfo")</h2><br/>
    
      ## Display value for all components in a loop.
      #foreach($index in [1..$componentSize] )                            
         ## Component general info

         ## Get the value for component name
         #set( $curKey = "component${index}-Name" )                
         #set( $component_componentName = "#valueOf($curKey)" )             
      
         ## Get the value for component type
         #set( $curKey = "component${index}-Type" )
         #set( $component_componentType = "#valueOf($curKey)" )             
      
         ## Get the value for parent of the component
         #set( $curKey = "component${index}-Parent" )
         #set( $component_parentComponent = "#valueOf($curKey)" )           
      
         ## Software specified
         ## Get the value for install path. This is applicable to software only.
         #set( $curKey = "component${index}-Software-Install-Path" )
         #set( $component_installPath = "#valueOf($curKey)" )               
      
         ## Get the value for group license. This is applicable to software only.
         #set( $curKey = "component${index}-Software-Group-License" )
         #set( $component_groupLicense = "#valueOf($curKey)" )                
      
         ## VM specified
         ## Get the value for cpu.
         #set( $curKey = "component${index}-cpu" )                           
         #set( $component_cpu = "#valueOf($curKey)" )                         
      
         ## Get the value for memory.
         #set( $curKey = "component${index}-memory" )
         #set( $component_memory = "#valueOf($curKey)" )                  
      
         ## Get the value for storage.
         #set( $curKey = "component${index}-storage" )
         #set( $component_storage = "#valueOf($curKey)" )                     
      
         ## Get the value for destruction date.
         #set( $curKey = "component${index}-DestructionDate" )
         #set( $component_destructionDate = "#valueOf($curKey)" )             
       
         ## Display all these values in a table.
         <table class="sectionGrid">                                        
            #renderRow("notification.email.extensions.component.name", "$component_componentName")
            #renderRow("notification.email.extensions.component.type", "$component_componentType")
            #renderRow("notification.email.extensions.component.parentComponent", "$component_parentComponent")          
            #renderRow("notification.email.extensions.component.installPath", "$component_installPath")
            #renderRow("notification.email.extensions.component.groupLicense", "$component_groupLicense")
            #renderRow("notification.email.extensions.cpus", "$component_cpu")
            #renderRow("notification.email.extensions.memory", "$component_memory", "MB")
            #renderRow("notification.email.extensions.storage", "$component_storage", "GB")      
            #renderRow("notification.email.extensions.destruction.date", "$component_destructionDate")      
         </table>
      <br/>
      #end ##For loop  
   #end ##end for if ($componentSize != 0)
#end ## end for #if( "$!component_size" != "" )
## Show Request Components Ends 

Now, let’s add some code to it to show data center.

## retrieve the value of custom property to get DataCenter.
## Add following lines after we get the value of destruction date.
#set( $curKey = "component${index}-DataCenter" )
#set( $component_dataCenter = "#valueOf($curKey)" )
 
## Display the value of custom property DataCenter. 
## Add following lines after we display destruction date.
#renderCustomRow("Data Center", "$component_dataCenter")

After these changes, the catalog request approval email would look like as follows:

Customized Catalog Item Request Approval Email
Customized Catalog Item Request Approval Email

 

Example – Customize All Emails By Adding Branding and Styles

First of all, let’s add company header to the emails. Update core/header.vm with following content:

<div class="header" align="right">
   <img  src="http://www.vmware.com/content/dam/digitalmarketing/vmware/en/files/images/wmrc/VMware_logo_gry_RGB_300dpi.jpg" style="width: 200px; height:70px;"/>
</div>

Generate any email, for example: request submission would look as follows:

Email with VMware branding/header
Email with VMware branding/header

 

Similarly, footer could be added to all the emails by adding content to footer.vm. Lets add some styles to the emails. Update styles.vm using following content. Later on, we will use these styles in our customizations.

<style type="text/css">
    body {
        font-family: Calibri, Helvetica, Sans-serif;
    }
    h1,
    h2 {
        margin: 0;
        color: #333333
    }
    a {
        color: #3EC6E0;
    }
    .layoutPage {
        margin-top: 12;
        border-bottom: solid 1 black
    }
    .layoutSection {
        margin-top: 6;
        margin-left: 4;
        border-bottom: solid 1 gray
    }
    .layoutRow {
        overflow: auto
    }
    .layoutRow:after {
        clear: both
    }
    .layoutColumn,
    .layoutText,
    .layoutField,
    .layoutPlaceholder,
    .layoutFlow {
        min-width: 20em
    }
    .label {
        white-space: nowrap;
        font-weight: bold;
        overflow: hidden;
        text-overflow: ellipsis
    }
    .layoutField,
    .layoutText,
    .layoutPlaceholder,
    .layoutFlow {} .layoutField .label {
        text-align: right;
        padding-right: 0.5em;
        float: left;
        width: 11em
    }
    .layoutField .value {
        text-align: left;
        padding-right: 0.5em;
        float: left
    }
    .layoutCell {
        min-width: 5em
    }
    .layoutDataTable table {
        border-collapse: collapse;
        width: 100%
    }
    .layoutDataTable table thead {
        border-style: solid;
        border-width: 1px;
        background-color: #DDDDDD
    }
    .layoutDataTable table thead th {
        font-size: 0.8em;
        text-align: left;
        padding: 2px
    }
    .layoutDataTable table tbody {
        border-style: solid;
        border-width: 1px
    }
    .layoutDataTable table tbody td {
        text-align: left;
        padding: 2px
    }
    .horizontalTable table,
    th td {
        border: 1px solid grey;
        border-collapse: collapse;
        width: 100%;
    }
    .horizontalTable thead {
        border-style: solid;
        border-width: 1px;
        background-color: #DDDDDD
    }
    .horizontalTable table thead th {
        font-size: 0.8em;
        text-align: left;
        padding: 2px;
    }
    .horizontalTable table tbody {
        border-style: solid;
        border-width: 1px
    }
    .horizontalTable table tbody td {
        text-align: left;
        padding: 2px;
    }
    .links {
        margin-top: 2em
    }
    .links ul {
        list-style-type: square;
    }
    .failedText {
        color: #ff0000
    }
    .successText {
        color: #00ff00
    }
    /* main_heading - begins */
     
    .main_heading1 {
        font-family: 'Brush Script MT', cursive;
        font-size: 30px;
        font-weight: 500;
        text-align: center;
    }
    .main_heading2 {
        font-family: Papyrus, fantasy;
        font-size: 15px;
        font-weight: 500;
    }
    /* main_heading - ends */
     
    .components * {
        margin: 0 auto;
        padding: 0;
        border: 0;
        font-size: 100%;
        font: inherit;
        vertical-align: baseline;
        box-sizing: border-box;
    }
    /* components - begins */
     
    .components {
        font-family: Papyrus, fantasy;
        margin: 10px auto;
        text-align: center;
        padding: 5px 5px;
        padding-right: 0;
    }
    .components_heading {
        font-family: Papyrus, fantasy;
        color: #9C9E9F;
        font-size: 1.3rem;
        font-weight: bold;
        margin-top: 0.5rem;
        margin-bottom: 0.5rem;
        font-weight: bold;
        text-align: center;
    }
    .components:before,
    .components:after {
        content: "";
        display: table;
    }
    .components:after {
        clear: both;
    }
    /* components - eneds */
    /* component - begins */
     
    .component {
        border-top-color: #FFFFFF;
        min-width: 210px;
        margin: 0 15px 32px;
        overflow: hidden;
        border-radius: 5px;
        float: left;
    }
    .component .title {
        background: #3EC6E0;
        color: #FEFEFE;
        line-height: 2.5;
        position: relative;
        padding: 0 3px 0 3px;
        font-size: 1.4rem;
    }
    .components .component .content {
        background: #53CFE9;
        position: relative;
        color: #FEFEFE;
        padding: 10px 0 5px 0;
    }
    .content:after,
    .content:before {
        left: 10%;
        border: solid transparent;
    }
    .component .content:after {
        border-color: rgba(136, 183, 213, 0);
        border-width: 2px;
        margin-left: -2px;
        border-top-color: #53CFE9;
    }
    /* component - end */
    /* component_name,ip,fields - begins */
     
    .component_name {
        margin-bottom: 0.625rem;
        font-size: 1.3rem;
        font-weight: bold;
    }
    .component_ip {
        font-style: italic;
        font-size: 1.1rem;
    }
    .component_fields {
        list-style-type: none;
        background: #FFFFFF;
        color: #9C9C9C;
        padding: 15px 5px 5px 5px;
        font-size: 0.9rem;
        border: solid #3EC6E0;
        border-bottom-left-radius: 0.5em;
        border-bottom-right-radius: 0.5em;
        border-width: 0 1px 1px 1px;
    }
    .component_fields li {
        padding: 3px 0;
        width: 100%;
        text-align: left;
    }
    .details_url {
        clear: both;
    }
    /* component_name,ip,fields - ends */
</style>

Lets customize request_catalogItem.vm that will affect both request submission email and approval email. Have a look at comments preceded by ## to understand the flow.


## This is same as it was in original template.
#set( $request_leasePeriod = "#valueOf('requestLeasePeriod')" )
#if( "$!request_leasePeriod" == "" || "$!request_leasePeriod.trim()" == "" ) 
   #set( $requestIsQuoteProvided = $formData.get("${keyPrefix}requestIsQuoteProvided") )
   #if( "$!requestIsQuoteProvided" != "" && $requestIsQuoteProvided ) 
      #set( $request_leasePeriod = "#msg('notification.email.extensions.request.unlimited')" )
   #end
#end

## Code to get number of instances requested. 
#set( $request_NumberOfInstances = "#valueOf('_number_of_instances')" )             
#if( "$!request_NumberOfInstances" == "" ) 
   #set( $request_NumberOfInstances = "1" )
#end
 
## This displays request information as well as scenario information. It relies on 3 variables - mainHeading1, userInformation and scenarioMessage.
<div class="main_heading">                                                           
   <div class="main_heading1"> $mainHeading1 </div>                                  
   <div class="main_heading2">
      <br/>$userInformation requested $request_NumberOfInstances instance(s) of <font color=#3EC6E0><b>#valueOf('catalogItem-Name')</b></font> on #valueOf('requestedDate'). The requested lease period is <font color=#3EC6E0><b>$request_leasePeriod</b></font>. $scenarioMessage
   </div> 
</div>
 
## Show Request Components using div and styles defined in styles.vm.
<br/>
<h2 class="components_heading">#msg("notification.email.extensions.component.componentInfo")</h2>

<div class="components">
#set( $component_size = "#valueOf('component-ComponentNos') " )
#set( $Integer = 0 )
#if( "$!component_size" != "" && "$!component_size.trim()" != "" )
   #set( $componentSize = $Integer.parseInt($component_size.trim()) )
   #if( $componentSize != 0 )

      ## Loop to get and show data for each component. The way to get the data in variables for each component is same as it was in original template. The only difference is the presentation.
      #foreach($index in [1..$componentSize] )                       
      
         ## Component general info                                     
        #set( $curKey = "component${index}-Name" ) 
        #set( $component_componentName = "#valueOf($curKey)" )
      
        #set( $curKey = "component${index}-Type" )
        #set( $component_componentType = "#valueOf($curKey)" )
      
        #set( $curKey = "component${index}-Parent" )
        #set( $component_parentComponent = "#valueOf($curKey)" )
      
        ## Software specified
        #set( $curKey = "component${index}-Software-Install-Path" )
        #set( $component_installPath = "#valueOf($curKey)" )
      
        #set( $curKey = "component${index}-Software-Group-License" )
        #set( $component_groupLicense = "#valueOf($curKey)" )
      
        ## VM specified
        #set( $curKey = "component${index}-cpu" )
        #set( $component_cpu = "#valueOf($curKey)" )
      
        #set( $curKey = "component${index}-memory" )
        #set( $component_memory = "#valueOf($curKey)" )
      
        #set( $curKey = "component${index}-storage" )
        #set( $component_storage = "#valueOf($curKey)" )
      
        #set( $curKey = "component${index}-Data Center" )
        #set( $component_dataCenter = "#valueOf($curKey)" )
 
        ## Different way to present the data using divs and styles.
        <div class="component">
           <h2 class="title"> $component_componentType </h2>
           <div class="content">
              <div class="component_name"> $component_componentName </div>
           </div>
            
           <div class="component_fields">
              <ul>
                 #notEmpty($component_cpu)
                    <li><span> $component_cpu CPU(s) </span></li>
                 #end
         
                 #notEmpty($component_memory) 
                    <li><span> $component_memory MB Memory </span></li>
                 #end
 
                 #notEmpty($component_storage)
                    <li><span> $component_storage GB Storage </span></li>
                 #end
 
                 #notEmpty($component_parentComponent)
                    <li><span> Parent: $component_parentComponent </span></li>
                 #end
 
                 #notEmpty($component_dataCenter)
                    <li><span> Data Center: $component_dataCenter </span></li>
                 #end
              </ul>
           </div>
        </div> ##end of component div
     #end ##For loop
  #end ##end for if ($componentSize != 0)
#end ## end for #if( "$!component_size" != "" )
</div> ## end of components div

The following are the contents of request submission template file and approval template file. They define all the three variables: mainHeading1, userInformation and scenarioMessage

Request Submission Email Template (csp.catalog.notifications.request.submission.vm)

#set( $mainHeading1 = "Did you request something ?" )
#set( $userInformation = "You have" )
#set( $scenarioMessage = "The request has been submitted successfully." )
 
#parse( 'extensions/displayRequest.vm' )

Approval Email Template (com.vmware.csp.core.approval.workitem.request.vm)

#set ( $keyPrefix = "source-source-" )
#set( $mainHeading1 = "A Request Needs Your Approval !" )
#set( $userInformation = "<font color=#3EC6E0><b>#valueOf('requestedBy')</b></font> has" )
#set( $scenarioMessage = "Do you approve request ?" )
 
#parse( 'extensions/displayRequest.vm' )

Now if we submit the request, the request submission email and approval email would look as follows:

Customized Request Submission Email
Customized Request Submission Email

 

Customized Approval Email
Customized Approval Email

 

Similarly, all other emails could be customized using css in styles.vm.

Customized Request Approved Email
Customized Request Approved Email

 

Customized Resource Activated Email
Customized Resource Activated Email

 

Customized Lease Modified Email
Customized Lease Modified Email

 

Customized Power Off Machine Approval Email
Customized Power Off Machine Approval Email

 

Customized Reconfigure Machine Approval Email
Customized Reconfigure Machine Approval Email

 

The customized templates as shown in the images can be downloaded from here. These examples are tested using outlook. If they don’t look good in your choice of email client, you need to update css in styles.vm

Permission for templates

When templates are deployed for the first time or a new template file is created, make sure it has right permissions. Right permissions could be set using following commands:

find /vcac -type d -exec chmod o+rx {} \;
find /vcac -type f -exec chmod o+r {} \;

Customizing emails on per tenant and per locale basis

Tenant specific templates can be defined in tenants/<tenantName> folder. If tenant specific template is not available, the template from defaults folder is used.

/vcac/templates/email/html/core/defaults/                  Contains Default core templates
/vcac/templates/email/html/core/tenants/Finance/           Contains Finance tenant specific core templates
/vcac/templates/email/html/extensions/defaults/            Contains Default extensions templates
/vcac/templates/email/html/extensions/tenants/Finance/     Contains Finance tenant specific extension templates

The folders (defaults/ or tenants/<tenantName>) can contain further subfolders for locale specific templates. For example:

/vcac/templates/email/html/core/defaults/fr/                     Contains Default core templates for french locale
/vcac/templates/email/html/core/defaults/                        Contains Default core templates for others locales
/vcac/templates/email/html/core/tenants/Finance/fr/              Contains Finance tenant specific fr locale core templates
/vcac/templates/email/html/core/tenants/Finance/                 Contains Finance tenant specific other locales core templates
 
Same is applicable for extension templates as well/

Supported Versions

The vRA supports email customization from 6.1 onwards. But, this blog is applicable to 7.0 onwards. There are two tar files mentioned in installation steps. Choose the right one for your version. A note for 7.0.0 and 7.0.1 users for non-english locale – If you want to use these templates for non-english locale, you need to update following strings with the desired locale specific strings.

  • In url_detail – Click here to view details
  • In request_resourceAction.vm – Resource Information
  • In displayResources.vm – Created On, Archive Days, Deployment, Deployment Information
  • In request_resourceAction.vm – Created On, Archive Days
  • In request_resourceAction_changeLease.vm – New Expiration Date
  • In request_catalogItem.vm – Deployment
  • In request_resourceAction.vm – Resource Action

For example: for Simplified Chinese – copy these files into extensions/defaults/zh/CN folder and update strings. Make sure to setup file permissions appropriately.