Introduction
While it is well known how to send dynamic email attachments using APEX in Salesforce, what is not well known is how to send dynamic email attachments using an email template (i.e., messaging:emailTemplate
) for use in workflow rules and email alerts. This tip describes how to attach dynamic PDFs to a Salesforce messaging email template.
How It Works
This endeavor requires several different pieces in order to work:
- A pdf page (e.g.
Invoice
) - A class for your PDF page (e.g.,
InvoiceController
) - A component to be referenced by the email template (e.g.,
IncludeAttachment
) - A class for the referenced component (e.g.,
IncludeAttachmentController
) - An email template containing the
messaging:emailTemplate
structure (e.g., Customer Invoice Email)
First, you want to create your pdf page. Here is a simple example; call this page Invoice
:
<apex:page controller="InvoiceController" applyHtmlTag="false"
applyBodyTag="false" showHeader="false" sidebar="false">
<html>
<span>Here is some dynamic PDF data: <apex:outputText
value="{!ContactName}" escape="false"/></span>
</html>
</apex:page>
Here is the controller behind this PDF page. Call this controller InvoiceController
:
public class InvoiceController {
public String ContactID {
get{
if(ContactID == null &&
ApexPages.currentPage().getParameters().get('contactId') != null){
ContactID = ApexPages.currentPage().getParameters().get('contactId');
}
return ContactID;
}
set;
}
public String ContactName {
get{
return [SELECT Name FROM Contact WHERE ID = :ContactID LIMIT 1].Name;
}
set;
}
}
Next, you want to create a component (VisualForce
or Lightening
) that will be referenced by the email template. Call this component IncludeAttachments
:
<apex:component controller="IncludeAttachmentsController" access="global">
<apex:attribute name="contactId"
description="Contact Id"
assignTo="{!contactObjectId}"
type="Id" />
<apex:outputText value="{!PageContents}" escape="false" />
</apex:component>
Now, we will need to create a controller called IncludeAttachmentsController
:
global class IncludeAttachmentsController {
global String PageContents{ get; set; }
global String contactObjectId{ get; set {
UpdateContents(value);
} }
public void UpdateContents(String contactObjectID)
{
try {
PageReference pageRef = Page.Invoice;
pageRef.getParameters().put('contactId', contactObjectID);
PageContents = pageRef.getContent().toString().replace
('<html style="display:none !important;">', '<html>');
} catch(exception ex) {
PageContents = 'An error has occurred while trying
to generate this invoice. Please contact customer service.';
}
}
}
Finally, we can create the email template itself; call this Customer Invoice Email:
<messaging:emailTemplate subject="Invoice Attached" recipientType="Contact"
relatedToType="Contact" replyTo="your@company.com">
<messaging:attachment renderAs="PDF" filename="Invoice.pdf">
<c:IncludeAttachments contactId="{!relatedTo.Id}"/>
</messaging:attachment>
<messaging:htmlEmailBody >
<html xmlns="http://www.w3.org/1999/xhtml">
Please find your invoice attached.
</html>
</messaging:emailTemplate>
Points of Interest
Notice the following line in your email template:
<c:IncludeAttachments contactId="{!relatedTo.Id}"/>
This line is what calls your component with the contactId
parameter. The component then calls its class, which passes the ContactId
parameter to the invoice controller (i.e., InvoiceController
). Notice how there is no renderAs="PDF"
on the Invoice
page; that is because you want this page to render as a string
into the IncludeAttachmentsController
. The renderAs="PDF"
attribute is located on the messaging:attachment
in the email template.
NOTE: If you experience an error, as in the entire email does not send in production but sends during testing, it could be that you are referencing external sites. For example, this will cause complete failure: <img src="https://www.somesite.com/someimage.png" />