Creating an HTML email within code

The PolicyCenter email subsystem sets the email content type to HTML only if the HTML property on the email is set to true. The following examples show how to work with HTML emails:
  • The EmailEnhancement example is an email enhancement that sets the HTML property based on the template file extension. The enhancement also sets other useful header values that exist in an email object.
  • The CreateEmailScreen example shows how to modify the base configuration CreateEmailScreen to add HTML template-related fields.
Note: If you are creating an email inline and you are sure that there is no possibility for an attack in the HTML, you can set the email HTML property directly. However, if you do so, ensure that you encode any user-supplied strings.

EmailEnhancement.gsx

The following code first populates a number of message headers, then provides a useEmailTemplate method that example PCF screen CreateEmailTemplate calls to set up the HTML email.

package gw.api.email

uses gw.api.util.DisplayableException
uses gw.document.TemplatePluginUtils
uses gw.plugin.email.IEmailTemplateDescriptor
uses java.io.StringReader
uses java.util.Map
uses java.util.HashSet
uses gw.api.util.LocaleUtil
uses gw.api.system.PLLoggerCategory

enhancement EmailEnhancement : gw.api.email.Email {
 
  // list of headers, note the value should be ascii-7 or encoded
  property get AUTOMATION_HEADER() : String { return "x-gw-Automation" }

  property get EXPIRATION_HEADER() : String { return "Expiry-Date" }
  property get REPLY_TO_ID_HEADER() : String { return "In-Reply-To" }
  property get ID_HEADER() : String { return "Message-ID" } 
  property get REF_IDS_HEADER() : String { return "References" } // space delimited 
  
  property get RETURN_RECEIPT_ADDR_HEADER() : String { return "Return-Receipt-To" }
  property get RETURN_READ_ADDR_HEADER() : String { return "Disposition-Notification-To" }

  property get THREAD_HEADER() : String { return "Thread-Topic" }
  property get THREAD_INDEX_HEADER() : String { return "Thread-Index" }

  property get PRIORITY_HEADER() : String { return "Priority" }
  property get PRIORITY_LOW() : String { return "5" }
  property get PRIORITY_HIGH() : String { return "1" }

  property get IMPORTANCE_HEADER() : String { return "Importance" }
  property get IMPORTANCE_LOW() : String { return "low" }
  property get IMPORTANCE_HIGH() : String { return "high" }

  property get SENSITIVITY_HEADER() : String { return "Sensitivity" }
  property get SENSITIVITY_CONFIDENTIAL() : String { return "Company-Confidential" }
  property get SENSITIVITY_HIGH() : String { return "high" }

   
  function useEmailTemplate(template : IEmailTemplateDescriptor, beans : Map<String,Object>) {
    this.Html = template.Html
    try {
      var locale = template.Locale
      if (locale == null) {
        locale = LocaleUtil.getDefaultLocale()
      }
      TemplatePluginUtils.resolveTemplates( locale,
          {new StringReader(template.Subject), new StringReader(template.Body)}, 
          // setup the symbol table for the template processing, this is the same between email and note
          \ iScriptHost -> {
            // load the symbols supplied by the caller
            var seen = new HashSet<String>()
            for (entry in beans.entrySet()) {
              var bean = entry.getValue()
              iScriptHost.putSymbol(entry.Key, typeof(bean) as String, bean)
              seen.add(entry.Key.toLowerCase())
            }
            // now load (or copy from other possible symbol names) the symbols that could be expected
            // cc: claim, ...
            // pc: policy, account, policyperiod, job, ...
            if (not seen.contains( "activity" )) {
              iScriptHost.putSymbol( "activity", Activity as String, null )
            }
            if (not seen.contains( "user" )) {
              iScriptHost.putSymbol( "user", User as String, User.util.CurrentUser )
            }
          },
          // process the result of the template expansion
          \ results -> {
            this.Subject = results[0]
            this.Body = results[1]
          } )
    } catch (e : Throwable) {
        PLLoggerCategory.MESSAGING_EMAIL.info("On ${template.getName()}", e)
        throw new DisplayableException("On ${template.getName()} caught ", e);
    }
  }
}

CreateEmailScreen.pcf

The following example code adds additional functionality to the base configuration PolicyCenter CreateEmailScreen PCF. This example version of the PCF screen adds HTML template-related fields to the screen, include a read-only view of the HTML template. This field (a TextAreaInput widget) contains a PostOnChange action that calls method executeTemplate, which, in turn, calls the useEmailTemplate method from EmailEnhancement.gsx.

Note: This example uses display keys that do not exist in the base configuration. You must add the missing display keys to display.properties to see the correct labels.
<?xml version="1.0"?>
<PCF
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="../../../../../../pcf.xsd">
  <Screen
    editable="true"
    id="CreateEmailScreen">
    <Require
      name="srcBean"
      type="Activity"/>
    <Require
      name="docContainer"
      type="Activity"/>
    <Require
      name="emailTemplateName"
      type="String"/>
    <Require
      name="documentsToSend"
      type="Document[]"/>
    <Variable
      initialValue="null"
      name="documentToSave"
      type="Document"/>
    <Variable
      initialValue="emailTemplateName == null"
      name="noDefaultTemplate"
      type="Boolean"/>
    <Variable
      initialValue="false"
      name="saveAsDocument"
      type="boolean"/>
    <Variable
      initialValue="false"
      name="showCC"
      type="boolean"/>
    <Variable
      initialValue="false"
      name="showBCC"
      type="boolean"/>
    <Variable
      initialValue="gw.api.util.LocaleUtil.getDefaultLanguageType()"
      name="language"
      type="LanguageType"/>
    <Variable
      initialValue="initializeSymbolTable()"
      name="symbolTable"
      type="java.util.Map&lt;String,Object&gt;"/>
    <Variable
      initialValue="new String()"
      name="content"
      type="String"/>
    <Variable
      initialValue="emailTemplateName == null ? null : fetchTemplate()"
      name="template"
      type="gw.plugin.email.IEmailTemplateDescriptor"/>
    <Variable
      initialValue="initNewEmail()"
      name="NewEmail"
      type="gw.api.email.Email"/>
    <Toolbar>
      <ToolbarButton
        action="sendEmailToRecipients(NewEmail)"
        available="true"
        id="ToolbarButton0"
        label="DisplayKey.get(&quot;Web.Activity.Email.SendEmail&quot;)"
        visible="true"/>
      <ToolbarButton
        action="CurrentLocation.cancel()"
        available="true"
        id="ToolbarButton1"
        label="DisplayKey.get(&quot;Web.Activity.Email.Cancel&quot;)"
        visible="true"/>
      <ToolbarDivider/>
      <PickerToolbarButton
        action="PickEmailTemplatePopup.push(createSearchCriteria())"
        id="EmailWorksheet_UseTemplateButton"
        label="DisplayKey.get(&quot;Web.Activity.Email.UseTemplate&quot;)"
        onPick="template = PickedValue; 
              language = gw.api.util.LocaleUtil.toLanguageType(PickedValue.Locale); 
              executeTemplate(NewEmail)"
        shortcut="P"
        visible="noDefaultTemplate"/>
    </Toolbar>
    <AlertBar
      id="NoDefaultTemplate"
      label="DisplayKey.get(&quot;Web.Email.Template.NotFound&quot;, emailTemplateName)"
      showConfirmMessage="false"
      visible="emailTemplateName != null and noDefaultTemplate"/>
    <DetailViewPanel>
      <InputColumn>
        <TypeKeyInput
          editable="true"
          id="Language"
          label="DisplayKey.get(&quot;Web.EmailTemplateSearch.Language&quot;)"
          required="true"
          value="language"
          valueType="typekey.LanguageType"
          visible="LanguageType.getTypeKeys( false ).Count &gt;  1 and emailTemplateName != null">
          <PostOnChange
                onChange="template = fetchTemplate(); executeTemplate(NewEmail)"/>
        </TypeKeyInput>
        <ListViewInput
          editable="true"
          id="ToRecipientLVInput"
          label="DisplayKey.get(&quot;Web.Activity.Email.ToRecipients&quot;)"
          labelAbove="true"
          visible="true">
          <Toolbar
            visible="true">
            <IteratorButtons
              addVisible="true"
              iterator="ToRecipientLV"
              removeVisible="true"/>
            <ToolbarDivider/>
          </Toolbar>
          <ListViewPanel
            editable="true"
            id="ToRecipientLV"
            visible="true">
            <RowIterator
              autoAdd="true"
              editable="true"
              elementName="ToRecipient"
              numEntriesRequired="1"
              numEntriesToAdd="1"
              toCreateAndAdd="var newEmailContact = new gw.api.email.EmailContact(); 
                    NewEmail.addToRecipient(newEmailContact); return newEmailContact;"
              toRemove="NewEmail.removeToRecipient( ToRecipient )"
              validationLabel="DisplayKey.get(&quot;Web.Activity.Email.ToRecipients&quot;)"
              value="NewEmail.ToRecipients?.toTypedArray()"
              valueType="gw.api.email.EmailContact[]">
              <Row
                editable="true">
                <TextCell
                  editable="true"
                  id="ToName"
                  label="DisplayKey.get(&quot;Web.Activity.Email.Name&quot;)"
                  maxChars="60"
                  numCols="15"
                  value="ToRecipient.Name"/>
                <TextCell
                  editable="true"
                  id="ToEmail"
                  label="DisplayKey.get(&quot;Web.Activity.Email.EmailAddress&quot;)"
                  numCols="15"
                  requestValidationExpression="VALUE == null ? 
                        DisplayKey.get(&quot;Web.Activity.Email
                        .Error.AddressForToRecipientRequried&quot;) : null"
                  required="true"
                  value="ToRecipient.EmailAddress"/>
              </Row>
            </RowIterator>
          </ListViewPanel>
        </ListViewInput>
        <ButtonInput
          action="showCC = true"
          id="ShowCCRecipients"
          labelAbove="true"
          value="DisplayKey.get(&quot;Web.Activity.Email.AddCCRecipients&quot;)"
          visible="!showCC"/>
        <ListViewInput
          editable="true"
          id="CcRecipientLVInput"
          label="DisplayKey.get(&quot;Web.Activity.Email.CCRecipients&quot;)"
          labelAbove="true"
          visible="showCC">
          <Toolbar
            visible="true">
            <IteratorButtons
              addVisible="true"
              iterator="CcRecipientLV"
              removeVisible="true"/>
          </Toolbar>
          <ListViewPanel
            editable="true"
            id="CcRecipientLV"
            visible="true">
            <RowIterator
              editable="true"
              elementName="CcRecipient"
              toCreateAndAdd="var newEmailContact = new gw.api.email.EmailContact(); 
                    NewEmail.addCcRecipient(newEmailContact); return newEmailContact;"
              toRemove="NewEmail.removeCcRecipient( CcRecipient )"
              value="NewEmail.CcRecipients?.toTypedArray()"
              valueType="gw.api.email.EmailContact[]">
              <Row
                editable="true">
                <TextCell
                  editable="true"
                  id="CcName"
                  label="DisplayKey.get(&quot;Web.Activity.Email.Name&quot;)"
                  numCols="15"
                  value="CcRecipient.Name"/>
                <TextCell
                  editable="true"
                  id="CcEmail"
                  label="DisplayKey.get(&quot;Web.Activity.Email.EmailAddress&quot;)"
                  numCols="15"
                  required="true"
                  value="CcRecipient.EmailAddress"/>
              </Row>
            </RowIterator>
          </ListViewPanel>
        </ListViewInput>
        <ButtonInput
          action="showBCC = true"
          id="ShowBCCRecipients"
          labelAbove="true"
          value="DisplayKey.get(&quot;Web.Activity.Email.AddBCCRecipients&quot;)"
          visible="!showBCC"/>
        <ListViewInput
          editable="true"
          id="BccRecipientLVInput"
          label="DisplayKey.get(&quot;Web.Activity.Email.BCCRecipients&quot;)"
          labelAbove="true"
          visible="showBCC">
          <Toolbar
            visible="true">
            <IteratorButtons
              addVisible="true"
              iterator="BccRecipientLV"
              removeVisible="true"/>
          </Toolbar>
          <ListViewPanel
            editable="true"
            id="BccRecipientLV"
            visible="true">
            <RowIterator
              editable="true"
              elementName="BccRecipient"
              toCreateAndAdd="var newEmailContact = new gw.api.email.EmailContact(); 
                    NewEmail.addBccRecipient(newEmailContact); return newEmailContact;"
              toRemove="NewEmail.removeBccRecipient( BccRecipient )"
              value="NewEmail.BccRecipients?.toTypedArray()"
              valueType="gw.api.email.EmailContact[]">
              <Row
                editable="true">
                <TextCell
                  editable="true"
                  id="BccName"
                  label="DisplayKey.get(&quot;Web.Activity.Email.Name&quot;)"
                  numCols="15"
                  value="BccRecipient.Name"/>
                <TextCell
                  editable="true"
                  id="BccEmail"
                  label="DisplayKey.get(&quot;Web.Activity.Email.EmailAddress&quot;)"
                  numCols="15"
                  required="true"
                  value="BccRecipient.EmailAddress"/>
              </Row>
            </RowIterator>
          </ListViewPanel>
        </ListViewInput>
        <InputDivider/>
        <CheckBoxInput
          editable="true"
          id="SaveAsDocument"
          value="saveAsDocument"
          valueLabel="DisplayKey.get(&quot;Web.Activity.Email.SaveAsDocument&quot;)"/>
      </InputColumn>
      <InputColumn>
        <TextInput
          editable="true"
          id="SenderName"
          label="DisplayKey.get(&quot;Web.Activity.Email.SenderName&quot;)"
          value="NewEmail.Sender.Name"/>
        <TextInput
          editable="true"
          id="SenderEmail"
          label="DisplayKey.get(&quot;Web.Activity.Email.SenderEmail&quot;)"
          value="NewEmail.Sender.EmailAddress"/>
        <TextInput
          editable="true"
          id="Subject"
          label="DisplayKey.get(&quot;Web.Activity.Email.Subject&quot;)"
          requestValidationExpression="VALUE == null ? 
                DisplayKey.get(&quot;Web.Activity.Email.Error.SubjectRequired&quot;) : null"
          required="true"
          value="NewEmail.Subject"/>
        <TextAreaInput
          editable="true"
          id="Content"
          label="template.ContentPrompt"
          numCols="60"
          numRows="10"
          required="false"
          value="content"
          visible="template.Html">
          <PostOnChange
                onChange="executeTemplate(NewEmail)"/>
        </TextAreaInput>
        <TextAreaInput
          editable="true"
          id="Body"
          label="DisplayKey.get(&quot;Web.Activity.Email.Body&quot;)"
          numCols="60"
          numRows="10"
          requestValidationExpression="VALUE == null ? 
                DisplayKey.get(&quot;Web.Activity.Email.Error.BodyRequired&quot;) : null"
          required="true"
          value="NewEmail.Body"
          visible="!template.Html"/>
        <ListViewInput
          editable="true"
          id="AttachedDocuments"
          label="DisplayKey.get(&quot;Web.Activity.Email.AttachedDocuments&quot;)">
          <Toolbar>
            <PickerToolbarButton
              action="PickExistingDocumentPopup.push(docContainer)"
              id="AddDocumentButton"
              label="DisplayKey.get(&quot;Web.Activity.Email.AddDocument&quot;)"
              onPick="NewEmail.addDocument(PickedValue)"
              shortcut="A"
              visible="true"/>
            <IteratorButtons
              addVisible="false"
              iterator="AttachedDocumentsLV"/>
          </Toolbar>
          <ListViewPanel
            editable="true"
            id="AttachedDocumentsLV">
            <RowIterator
              editable="true"
              elementName="AttachedDocument"
              toRemove="NewEmail.removeDocument( AttachedDocument )"
              value="NewEmail.Documents?.toTypedArray()"
              valueType="entity.Document[]">
              <Row>
                <TextCell
                  editable="true"
                  id="Document"
                  label="DisplayKey.get(&quot;Web.Activity.Email.DocumentName&quot;)"
                  value="AttachedDocument.Name"/>
              </Row>
            </RowIterator>
          </ListViewPanel>
        </ListViewInput>
      </InputColumn>
    </DetailViewPanel>
    <TemplatePanel>
      <TemplatePanelContents><![CDATA[   
            <% if (template != null && template.Html) printContent(NewEmail.Body, false) %>]]>
      </TemplatePanelContents>
    </TemplatePanel>
    <Code><![CDATA[function initializeSymbolTable() : java.util.Map<String,Object> {
  return { "activity" -> srcBean }
}

function createSearchCriteria() : gw.api.email.EmailTemplateSearchCriteria {
  var rtn = new gw.api.email.EmailTemplateSearchCriteria()
  rtn.Language = language
  rtn.AvailableSymbols = symbolTable.Keys?.toTypedArray()
  return rtn
}

function initNewEmail() : gw.api.email.Email {
  var rtn = new gw.api.email.Email()
  if (documentsToSend != null) {
    for (document in documentsToSend) {
      rtn.addDocument( document )
    }
  }
  if (template != null) {
    executeTemplate(rtn)
  }
  return rtn
}

function fetchTemplate() : gw.plugin.email.IEmailTemplateDescriptor {
  template = gw.plugin.Plugins.get(gw.plugin.email.IEmailTemplateSource)
        .getEmailTemplate(gw.api.util.LocaleUtil.toLanguage(language), emailTemplateName);
  if (template == null) {
    noDefaultTemplate = true
    throw new gw.api.util.DisplayableException(DisplayKey
          .get("Web.Activity.EmailTemplate.Language", emailTemplateName, language))
  }
  return template
}

function executeTemplate( email : gw.api.email.Email) {
  email.useEmailTemplate(template, { "activity" -> srcBean, "content" -> content })
}

function sendEmailToRecipients(emailToSend : gw.api.email.Email) {
  var warnings = gw.api.email.EmailUtil.emailContentsValid(emailToSend)
  if (warnings.length > 0) {
    throw new gw.api.util.DisplayableException(warnings)
  }
  if (saveAsDocument) {
    var templatePlugin = gw.plugin.Plugins.get(gw.plugin.document.IDocumentTemplateSource)
    var emailSentDocTemplate = templatePlugin
          .getDocumentTemplate("EmailSent.gosu.htm", gw.api.util.LocaleUtil.getDefaultLocale())
    if (emailSentDocTemplate == null) {
      throw new gw.api.util.DisplayableException("Could not save email as a document 
            because the \"EmailSent.gosu.htm\" does not exist!")
    } else {
      documentToSave = documentToSave != null ? documentToSave : new Document()
      documentToSave.Name  = emailToSend.Subject
      documentToSave.MimeType = emailSentDocTemplate.MimeType
      documentToSave.Type = typekey.DocumentType.get(emailSentDocTemplate.TemplateType)
      documentToSave.Section = typekey.DocumentSection
            .get(emailSentDocTemplate.getMetadataPropertyValue( "section" ) as String)
      documentToSave.SecurityType = typekey.DocumentSecurityType
            .get(emailSentDocTemplate.DefaultSecurityType)
      documentToSave.Status = TC_FINAL
      var recpName = emailToSend.ToRecipients.first().Name
      documentToSave.Recipient = recpName == null ? emailToSend.ToRecipients.first().EmailAddress : recpName
      documentToSave.Activity = docContainer
      documentToSave.DateCreated = gw.api.util.DateUtil.currentDate()
      documentToSave.Author = User.util.CurrentUser.DisplayName
      documentToSave.Inbound = false

      var paramMap = new java.util.HashMap()
      paramMap.put("User", User.util.CurrentUser)
      paramMap.put("Email", emailToSend)
      paramMap.put("DateSent", gw.api.util.DateUtil.currentDate())
      gw.document.DocumentProduction
            .createAndStoreDocumentSynchronously(emailSentDocTemplate, paramMap, documentToSave);
    }
  } else if (documentToSave != null) {
    documentToSave.remove();
  }
  gw.api.email.EmailUtil.sendEmailWithBody(srcBean, emailToSend);
  // it didn't throw so reset email activity.EmailTemplate so that other templates can be used
  if (emailTemplateName != null and srcBean typeis Activity) {
    if (srcBean.EmailTemplate == emailTemplateName) {
      srcBean.EmailTemplate = null
    }
  }
  CurrentLocation.commit();
}]]></Code>
  </Screen>
</PCF>