Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@douglascayers
Created January 4, 2017 05:32
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save douglascayers/2edc6764333b9df1461eccd9b4f2ff5f to your computer and use it in GitHub Desktop.
Save douglascayers/2edc6764333b9df1461eccd9b4f2ff5f to your computer and use it in GitHub Desktop.
Example Apex classes to enable Process Builder to send emails without using Email Alerts + Templates or launching a Flow.
/**
* Developed by Doug Ayers
* douglascayers.com
*
* Designed to be used by SendEmailInvocable class when sending
* several emails but need to stay within the apex governor limits
* of how many emails can be sent per transaction. Call this batchable
* with all the emails to send and set the batch size to max per transaction.
* https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_apexgov.htm
*
* Note, since Messaging.SingleEmailMessage is not serializable then we cannot use it
* as the object returned by our batch start method. Instead, I've opted to use the custom
* class SendEmailInvocable.Request as custom classes are serializable.
*
* Example:
* Database.executeBatch( new SendEmailBatchable( requests ), Limits.getLimitEmailInvocations() );
*/
public with sharing class SendEmailBatchable implements Database.Batchable<SendEmailInvocable.Request> {
private List<SendEmailInvocable.Request> requests { get; set; }
public SendEmailBatchable( List<SendEmailInvocable.Request> requests ) {
this.requests = requests;
}
public List<SendEmailInvocable.Request> start( Database.BatchableContext context ) {
System.debug( 'SendEmailBatchable.start: ' + context );
return this.requests;
}
public void execute( Database.BatchableContext context, List<SendEmailInvocable.Request> requests ) {
System.debug( 'SendEmailBatchable.execute: ' + context );
Boolean allOrNone = false;
List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
for ( SendEmailInvocable.Request req : requests ) {
messages.add( convertToMessage( req ) );
}
List<Messaging.SendEmailResult> results = Messaging.sendEmail( messages, allOrNone );
for ( Messaging.SendEmailResult result : results ) {
if ( !result.isSuccess() ) {
for ( Messaging.SendEmailError err : result.getErrors() ) {
System.debug( LoggingLevel.ERROR, err );
}
}
}
}
public void finish( Database.BatchableContext context ) {
System.debug( 'SendEmailBatchable.finish: ' + context );
}
// -----------------------------------------------------------------
private static final String CONTACT_KEY_PREFIX = Contact.sObjectType.getDescribe().getKeyPrefix();
private static final String LEAD_KEY_PREFIX = Lead.sObjectType.getDescribe().getKeyPrefix();
private static final String CONTENT_DOCUMENT_KEY_PREFIX = ContentDocument.sObjectType.getDescribe().getKeyPrefix();
private static Messaging.SingleEmailMessage convertToMessage( SendEmailInvocable.Request req ) {
Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
if ( String.isNotBlank( req.whoId ) ) {
String whoKeyPrefix = req.whoId.getSObjectType().getDescribe().getKeyPrefix();
message.setTargetObjectId( req.whoId );
message.setSaveAsActivity( ( whoKeyPrefix == CONTACT_KEY_PREFIX || whoKeyPrefix == LEAD_KEY_PREFIX ) );
}
if ( String.isNotBlank( req.whatId ) ) {
message.setWhatId( req.whatId );
if ( String.isBlank( message.getTargetObjectId() ) ) {
message.setSaveAsActivity( true );
}
}
if ( String.isNotBlank( req.toAddresses ) ) {
message.setToAddresses( req.toAddresses.deleteWhitespace().split( ',' ) );
}
if ( String.isNotBlank( req.ccAddresses ) ) {
message.setCcAddresses( req.ccAddresses.deleteWhitespace().split( ',' ) );
}
if ( String.isNotBlank( req.bccAddresses ) ) {
message.setBccAddresses( req.bccAddresses.deleteWhitespace().split( ',' ) );
}
if ( String.isNotBlank( req.orgWideEmailId ) ) {
message.setOrgWideEmailAddressId( req.orgWideEmailId );
}
if ( String.isNotBlank( req.senderName ) ) {
message.setSenderDisplayName( req.senderName );
}
if ( String.isNotBlank( req.replyTo ) ) {
message.setReplyTo( req.replyTo );
}
if ( String.isNotBlank( req.subject ) ) {
message.setSubject( req.subject );
}
if ( String.isNotBlank( req.textBody ) ) {
message.setPlainTextBody( req.textBody );
}
if ( String.isNotBlank( req.htmlBody ) ) {
message.setHtmlBody( req.htmlBody );
}
if ( String.isNotBlank( req.fileIds ) ) {
// common mistake is to provide ContentDocument ID instead of ContentVersion ID
// if someone copies the Chatter File ID from the URL.
// to help folks out, we'll convert to correct id.
List<String> fileIds = req.fileIds.deleteWhitespace().split( ',' );
List<String> contentDocumentIds = new List<String>(); // to lookup latest published version id
List<String> entityAttachmentIds = new List<String>(); // to add to the email message
for ( String fileId : fileIds ) {
String fileKeyPrefix = fileId.left( 3 );
if ( fileKeyPrefix == CONTENT_DOCUMENT_KEY_PREFIX ) {
contentDocumentIds.add( fileId );
} else {
entityAttachmentIds.add( fileId );
}
}
if ( contentDocumentIds.size() > 0 ) {
for ( ContentDocument cd : [ SELECT id, latestPublishedVersionId FROM ContentDocument WHERE id IN :contentDocumentIds ] ) {
entityAttachmentIds.add( cd.latestPublishedVersionId );
}
}
if ( entityAttachmentIds.size() > 0 ) {
message.setEntityAttachments( entityAttachmentIds );
}
}
return message;
}
}
/**
* Developed by Doug Ayers
* douglascayers.com
*
* Exposes the Apex Messaging.sendEmail( Messaging.SingleEmailMessage ) capability
* to Process Builder and Flow as an invocable method without having to use Email Alerts or Templates.
* https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_email_outbound_single.htm
*
* This is not a replacement for Email Alerts and Templates but rather a tool to make
* sending emails from Process Builder a bit easier without extra configuration of
* creating an Email Alert + Template or launching a Flow and using its Send Email action.
*
* Jeremiah Dohn developed similar solution using ProcessPlugin for Flow to support HTML emails.
* http://www.sfdccloudninja.com/downloads/use-of-html-email-plugin/
*
* Please vote for this idea that Flow can send HTML emails:
* https://success.salesforce.com/ideaView?id=08730000000DkRoAAK
*
* Be aware of this known issue that use of BR() function in text formula expressions
* will render as "_BR_ENCODED_" instead of a newline when used in Process Builder or Flow:
* https://success.salesforce.com/issues_view?id=a1p300000008YkZAAU
*/
public with sharing class SendEmailInvocable {
@InvocableMethod(
label = 'Send Email'
description = 'Sends a text and/or html email to recipients without need of Email Alert or Template. If set the Who ID or What ID then Activity is also created.'
)
public static void execute( List<Request> requests ) {
// Note, this is bound by Apex governor limits of no more than 10 emails can be sent per transaction.
// Therefore, all the requests are batched together and sent within apex transaction limits for bulk-friendliness.
// https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_apexgov.htm
Database.executeBatch( new SendEmailBatchable( requests ), Limits.getLimitEmailInvocations() );
}
// -----------------------------------------------------------------
/**
* Note that for fields we want to receive multiple values for they are Strings and we expect comma-delimited values rather than a List.
* This is because when calling invocable methods from Process Builder you are not able to provide a List of values, just a single value.
* In Flow, however, you can build up a Collection Variable and pass in a List of values.
* So to make this solution more user-friendly to Process Builder we accept comma-delimited strings of values and in the code we split them
* into List as appropriate.
*/
public class Request {
@InvocableVariable(
label = 'Who ID'
description = 'Email recipient and who the email Activity will be related to (User, Contact, or Lead). At least one of Who ID, To, Cc, Bcc Addresses must be set.'
)
public ID whoId;
@InvocableVariable(
label = 'What ID'
description = 'What the email Activity will be related to (Account, Case, Opportunity, etc.). Cannot be set if Who Id is a User record.'
)
public ID whatId;
@InvocableVariable(
label = 'To Addresses'
description = 'Comma-delimited list of email addresses. At least one of Who ID, To, Cc, Bcc Addresses must be set.'
)
public String toAddresses;
@InvocableVariable(
label = 'Cc Addresses'
description = 'Comma-delimited list of email addresses. At least one of Who ID, To, Cc, Bcc Addresses must be set.'
)
public String ccAddresses;
@InvocableVariable(
label = 'Bcc Addresses'
description = 'Comma-delimited list of email addresses. At least one of Who ID, To, Cc, Bcc Addresses must be set.'
)
public String bccAddresses;
@InvocableVariable(
label = 'Org-Wide Email Address ID'
description = 'ID of the organization-wide email address to use. Cannot be used if "Sender Name" is set.'
)
public String orgWideEmailId;
@InvocableVariable(
label = 'Sender Name'
description = 'The name that appears on the From line of the email. Cannot be set if "Org-Wide Email Address ID" is set.'
)
public String senderName;
@InvocableVariable(
label = 'Reply To'
description = 'The email address that receives the message when a recipient replies.'
)
public String replyTo;
@InvocableVariable(
label = 'Subject'
description = 'The email subject line.'
)
public String subject;
@InvocableVariable(
label = 'Text Body'
description = 'The text version of the email. At least one of "Text Body" or "Html Body" must be set.'
)
public String textBody;
@InvocableVariable(
label = 'Html Body'
description = 'The html version of the email. At least one of "Text Body" or "Html Body" must be set.'
)
public String htmlBody;
@InvocableVariable(
label = 'File Attachment IDs'
description = 'Comma-delimited list of Document IDs and/or ContentVersion IDs (aka Chatter Files) to attach to email. Standard Attachment IDs are not supported.' )
public String fileIds;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment