In Salesforce, automating email notifications based on the Opportunity Stage is a common requirement. While Flows can handle sending emails to fields directly on the Opportunity, things get a bit tricky when we need to send emails to related Contact Roles. To accomplish this using Flow, you need to loop through the Contact Roles manually, which can be cumbersome.
In this blog, we’ll discuss how you can use Apex to efficiently send email alerts to the Opportunity Owner, a custom Contact field, and all Contact Roles associated with the Opportunity.
The Challenge
The standard Salesforce Flow can easily send emails to fields directly on the Opportunity, like the Opportunity Owner. However, we run into some limitations when we need to send emails to additional recipients, such as contacts associated with the Opportunity (Opportunity Contact Roles).
A workaround with Flow would be to loop through all the Contact Roles, but using Apex to handle this logic would be more efficient.
The Apex Solution
Using an Apex Trigger and Class, we can automate the sending of email alerts when the Opportunity Stage changes to “Won.” Here’s how we can break down the process:
Apex Trigger
This trigger runs after the Opportunity record is updated and checks if the Opportunity has moved to the “Won” stage. If the stage has changed from something other than “Won” to “Won,” the Apex helper class will be called to send emails.
Trigger Code
trigger OpportunityTrigger on Opportunity (before update, after update) {
List oppsMovingToWon = new List();
for (Opportunity opp : Trigger.new) {
Opportunity oldOpp = (Opportunity)Trigger.oldMap.get(opp.Id);
if (opp.StageName == 'Closed Won' && oldOpp.StageName != 'Closed Won') {
oppsMovingToWon.add(opp);
}
}
if (!oppsMovingToWon.isEmpty()) {
OpportunityEmailHelper.sendEmailOnStageChange(oppsMovingToWon);
}
}
Apex Class
In the Apex class OpportunityEmailHelper, we query the necessary Opportunity fields, including the Opportunity Contact Roles, and build the list of email addresses for the recipients.
Apex Class Code
public class OpportunityEmailHelper {
public static void sendEmailOnStageChange(List opportunities) {
List emailsToSend = new List();
// Fetch the email template
EmailTemplate emailTemplate = [
SELECT Id, Subject, Body
FROM EmailTemplate
WHERE DeveloperName = 'Email Template'
LIMIT 1
];
if (emailTemplate == null) {
return;
}
Set OpportunityIds = new Set();
for (Opportunity oppRec : opportunities) {
OpportunityIds.add(oppRec.Id);
}
List oppRecordList = [
SELECT Id, OwnerId, CloseDate, StageName, Type, RecordType.Name,
Signer__c, Signer__r.Email,
(SELECT Contact.Email FROM OpportunityContactRoles)
FROM Opportunity
WHERE StageName = 'Closed Won'
AND (/* ADD FILTERS BASED ON YOUR NEED */)
AND Id IN :OpportunityIds
];
for (Opportunity opp : oppRecordList) {
Opportunity oldOpp = (Opportunity)Trigger.oldMap.get(opp.Id);
if (opp.StageName == 'Closed Won' && oldOpp.StageName != 'Closed Won') {
String toAddresses = '';
String ccAddresses = '';
// Add Opportunity Owner to the 'to' list
User owner = [SELECT Email FROM User WHERE Id = :opp.OwnerId LIMIT 1];
if (owner.Email != null) {
toAddresses += owner.Email + '; ';
}
// Add Customer Signer to the 'to' list
if (opp.Customer_Signer__c != null && opp.Customer_Signer__r.Email != null) {
toAddresses += opp.Customer_Signer__r.Email + '; ';
}
// Loop through Opportunity Contact Roles and add them to the 'cc' list
for (OpportunityContactRole ocr : opp.OpportunityContactRoles) {
if (ocr.Contact.Email != null) {
ccAddresses += ocr.Contact.Email + '; ';
}
}
// Remove trailing semicolons
toAddresses = toAddresses.trim().endsWith(';') ? toAddresses.trim().substring(0, toAddresses.length() - 1) : toAddresses;
ccAddresses = ccAddresses.trim().endsWith(';') ? ccAddresses.trim().substring(0, ccAddresses.length() - 1) : ccAddresses;
// Create the email and send it
if (!String.isEmpty(toAddresses) || !String.isEmpty(ccAddresses)) {
Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
email.setTemplateId(emailTemplate.Id);
email.setTargetObjectId(opp.Customer_Signer__c);
if (!String.isEmpty(toAddresses)) {
email.setToAddresses(toAddresses.split(';'));
}
if (!String.isEmpty(ccAddresses)) {
email.setCcAddresses(ccAddresses.split(';'));
}
email.setSaveAsActivity(true);
emailsToSend.add(email);
}
}
}
if (!emailsToSend.isEmpty()) {
Messaging.sendEmail(emailsToSend);
}
}
}
How This Works:
Trigger:
When the Opportunity stage changes to “Won,” the trigger detects it and calls the OpportunityEmailHelper.sendEmailOnStageChange method.
Apex Class (Email Helper):
- The method queries the Opportunity and related Contact Roles.
- It adds the Opportunity Owner and Customer Signer to the “To” addresses.
- It loops through the Opportunity Contact Roles and adds those contacts to the “CC” list.
- Using an email template, it sends the email to the gathered recipients.
Why Use Apex for This?
- Efficiency: It reduces the need for looping in Flows, which can be cumbersome and inefficient.
- Scalability: The logic can handle large numbers of Contact Roles and different Opportunity types.
- Customization: With Apex, you have complete control over the email-sending logic and can easily adapt it to different business needs.
Conclusion
This solution is powerful for handling complex email alerts in Salesforce. Apex allows you to send emails to multiple recipients, including the Opportunity Owner, Contact Roles, and other custom fields. It’s a great way to enhance your Salesforce automation and inform all relevant stakeholders when an Opportunity reaches a critical stage.
If you want to explore this in more detail or have specific questions, feel free to comment below!