38

I am looking to assemble an HTML Letterhead Email Template for an email service integration project.

There are two sObjects that hold this information - EmailTemplate and BrandTemplate.

BrandTemplate.value = broken HTML (styles aren't associated to tags) Letterhead with CDATA tags throughout to hold the email Template info

EmailTemplate.HTMLValue = User-created HTML email template

I've been trying to merge the two (don't need to worry about merge fields / codes) with little success.

I have tried turning both into XML, navigating the BrandTemplate.value field, but Salesforce does not natively support CDATA. I've also tried the polar opposite - stripping everything into text and doing replaces and substrings to assemble, but that also fails.

My end goal is to have a completed single HTML document consisting of both the BrandTemplate and EmailTemplate values.

Any hints / thoughts / clues?

6
  • 1
    How exactly does it fail?
    – jkraybill
    Commented Jul 31, 2012 at 23:47
  • 1
    There are two specific things that I'm running into issues with. One - if you look at the structure of the BrandTemplate.value field, there are CDATA sections in three main areas - header, body, and footer. Firstoff, I need to insert my EmailTemplate.HTMLValue into the body CDATA section. Two - all of the styling and image tags are not properly formatted in the BrandTemplate.value field, so I need to go in and deal with that too. If I had a full XML library that supported CDATA - not a prob, SF does not natively. Commented Aug 1, 2012 at 14:07
  • 1
    I've had some success in parsing / manipulating email templates using complicated regular expressions and String.replaceAll, does that offer a potential solution?
    – jkraybill
    Commented Aug 2, 2012 at 0:31
  • 1
    There are two major things that i'm having issues with with the replaceAll method - the styles are in their own tags and I need to merge them with the immediately preceding tag, and the image tags have CDATA outside the tag with the actual src value, so I have to get them in the previous tag as well. All in all a major PITA. Commented Aug 2, 2012 at 4:00
  • 1
    I'd like to know how you managed to achieve this in the end.
    – Jan J
    Commented Jan 4, 2013 at 4:35

6 Answers 6

55

Salesforce hacking time: I had this exact requirement, and was stuck for a while because of the weird connection between EmailTemplate and BrandTemplate, and the inability to leverage a merging/parsing function however, I found a solution, this is far from a clean solution, but it seems to be working perfectly.

First what you will do is, create a single email message (like you would send it from apex):

// Here we will build the single email message
Messaging.reserveSingleEmailCapacity(1);
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[]{'[email protected]'};
mail.setToAddresses(toAddresses);
mail.setUseSignature(false);
mail.setSaveAsActivity(false);
mail.setSenderDisplayName('MMPT');
mail.setTargetObjectId(UserInfo.getUserId());
mail.setTemplateId(sendTemplate.Id);

What we will do next is trick salesforce in sending the single email message, by setting a savepoint, sending the message and rolling back. The single email will not be send because of the rollback.

Savepoint sp = Database.setSavepoint();
Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
Database.rollback(sp);

But now the beauty comes, because we triggered the sendEmail method, the email template got merged with the brandtemplate and the parsing took place. And you can find the parsed versions of your template components in the next variables (Subject, Text and HTML)

String mailTextBody = mail.getPlainTextBody();
String mailHtmlBody = mail.getHTMLBody();
String mailSubject = mail.getSubject();

This is far from what salesforce meant to do with single email message, and since we cannot leverage the platform parsing and merging functions, this did gave me a solution to this problem unfortunately through some hacking.

14
  • 1
    This trick is not working in for loop as i am trying to send 100 mails at a time using third party email sending api
    – Sunny
    Commented Oct 9, 2014 at 15:18
  • 1
    I am getting the bellow error when i call this from batch apex "you have uncommitted work pending. please commit or rollback before calling out salesforce"
    – Sunny
    Commented Oct 10, 2014 at 9:43
  • 1
    That error message has nothing to do with this way of hacking your templates together, it is because you do a DML statement (insert, update, upsert, delete) BEFORE doing a callout. All DML statements must be done after the callout
    – pjcarly
    Commented Oct 10, 2014 at 10:05
  • 1
    Hi @PJC, Actually i am using this one in batch apex calling the API callout by making for loop and i tried with making batch size as 1 but again I am getting same issue. is not it posible to place this logic in batch apex?
    – Sunny
    Commented Oct 11, 2014 at 17:57
  • 1
    Awesome idea. Never thought something like that is possible. Looks like this is the only possible solution for an template with letterhead.
    – Patlatus
    Commented Feb 26, 2015 at 13:26
54

Summer 16 introduces two new methods that (finally) eliminate the clever hack by @pjcarly:

For VF, HTML, and Plain Text templates:

Messaging.SingleEmailMessage email = 
            Messaging.renderStoredEmailTemplate(templateId, whoId, whatId);

To get just the merged results from the templateBody (and any errors)

List<Messaging.RenderEmailTemplateBodyResult> resList = 
        Messaging.renderEmailTemplate(String whoId, String whatId, List<String> bodies)

where bodies is a list of strings corresponding to the value of EmailTemplate.body as queried from the database

for (EmailTemplate et: [select Body from EmailTemplate where DeveloperName IN ('foo','bar')])
  bodies.add(et.Body); 
3
  • 1
    I think your answer needs to be updated as your select statement for bodies returns a list of emailtemplates that need to be iterated through so that you can add the body field to a list of strings. Commented Mar 31, 2017 at 17:58
  • 5
    @techbusinessman - sheesh, no kidding, fixed, Thanks!
    – cropredy
    Commented Apr 1, 2017 at 17:59
  • 2
    This is definitely the way to go now and should be marked as the correct answer
    – ministe
    Commented Jul 30, 2018 at 11:45
6

When accessing templates and needing multiple objects I will
- select the template from the database
- define a merge syntax, use regular expressions "{!([A-z_]+)}" to get a key name - pass a map of data to replace the key (if exists) with data

you now have a template which is just a string. you can push the template to a VF page with or send an email manually in code.

code example:

public static Map<String,String> merge_template(Map<String,String> obj, EmailTemplate email){
    Map<String,String> data = new Map<String,String>();
    String html = email.HtmlValue;
    if(html!=null){
        html = m(html,obj,Pattern.compile('(\\{![A-Za-z_]+\\})').matcher(html));
        data.put('HtmlValue',html);
    }
    else{
        data.put('HtmlValue','');
    }
    String body = email.Body;
    String subject = email.Subject;
    body = m(body,obj,Pattern.compile('(\\{![A-Za-z_]+\\})').matcher(body));

    data.put('Body',body);

    return data;

}
private static String m(String body,Map<String,String> obj,Matcher m){
    while(m.find()){
      String merge_field = m.group(0);
      String field = merge_field.substring(1,merge_field.length()-1);
      String value;
      try{
        value = (String) obj.get(field);
      }
     catch(Exception e){
        value='';
     }
     if(value!=null){
        body = body.replace(merge_field,value);
     }
     else{
        body = body.replace(merge_field,'');
     }
    }
    return body;
}

public static Map<String,String> build(String template,Map<String,String> data){
    EmailTemplate[] tpls = [SELECT HtmlValue,Body,Subject FROM EmailTemplate WHERE DeveloperName = :template];
    if(tpls.isEmpty() == true){
        return "";
    }
    return merge_template(data,tpls.get(0);
}
5
  • 2
    Do you have an example of this? Commented Aug 8, 2012 at 18:04
  • 1
    That's pretty cool ebt - let me pop around and let you know what I putter out. Thanks! Commented Aug 17, 2012 at 16:26
  • 1
    Hello, That looks great - it's exactly what I was looking for. Could you please explain to me what UtilsList.getFirst does? I get the error "Save Error: Variable does not exist:UtilsList". Thanks in advance.
    – user8737
    Commented Jun 19, 2014 at 10:43
  • 1
    I used getFirst to cut down on the boilerplate conversion from a List to single object, but it turned out to be ill conceived. I updated the code to remove that reference.
    – ebt
    Commented Jun 19, 2014 at 16:43
  • Given code does not work for me; first it does not even compile due to <code>return "";</code> where Map<String,String> value is expected, not a string. Also "{!([A-z_]+)}" regex does not work for me at all because it does not contain a dot and usually everybody has merge field like {!Custom_Object__c.Custom_Field_1__c}
    – Patlatus
    Commented Feb 26, 2015 at 13:21
2

try to user the setTemplateId field of Mail.

Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
EmailTemplate et=[SELECT id,name, subject,HtmlValue FROM EmailTemplate where name='xyz'];
mail.setTemplateId(et.id);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

No need to do any custom parsing.

1

You should be able to create a VF Email Template, and embed a VF Component within it to query those sObjects with an Apex controller.

1
  • That is correct, which I have done. However - the merging of those two values from the two sObjects is the problem. In your org, look at the values in those two fields and that's what I'm trying to merge together. Getting it to display in a Component isn't the question. =) Commented Aug 1, 2012 at 14:04
1

I had some other idea to do this.

EmailTemplate template = [
SELECT Id, Name, Subject, Body, HtmlValue
FROM EmailTemplate
WHERE Name = 'My Name'];
templateId = template.Id;

Custom_Object__c obj = [
SELECT Name, Custom_Field_1__c, Custom_Field_2__c
FROM Custom_Object__c
WHERE Id = :my.Id];

Map<String, String> data = new Map<String, String>();
for(String key:new List<String> {'Name', 'Custom_Field_1__c', 'Custom_Field_2__c' })
{
    data.put('{!Custom_Object__c.' + key + '}', (String) obj.get(key) );
    System.debug( LoggingLevel.ERROR,'@@@ key : ' + key );     
    System.debug( LoggingLevel.ERROR,'@@@ obj.get(key) : ' + obj.get(key) );   

}
data.put('User.FirstName',UserInfo.getFirstName());
data.put('User.LastName',UserInfo.getLastName());
String body = template.HtmlValue;
System.debug(LoggingLevel.ERROR,body);
System.debug(LoggingLevel.ERROR,'@@@  template.HtmlValue: ' +  template.HtmlValue );
System.debug( LoggingLevel.ERROR,'@@@   template.Subject: ' +   template.Subject );

String subj = template.Subject;
for(String key:data.keySet())
{
    body = body.replace(key, data.get(key));
    subj = subj.replace(key, data.get(key));
}

Unfortunately it does not for email templates with letter heads.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .