-
-
Save nikitakarpenkov/06d939900daaf9951e066cf8aaaee662 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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