Last updated on September 15, 2022
DESCRIPTION
This article walks you through sending the quote PDF attached to the email notification sent via flow. If your quote PDFs are stored related to the quote record and you want to send the PDF to the customer using flow, then this article is for you. You can create quote PDFs using the standard quote creation feature in Salesforce.
You can send an email with the quote pdf attached to it so that their customer can verify the quote items when a quote is activated. Since we can not attach the files from the records while sending an email using email alerts or flows. So I came up with a solution by creating an apex invocable class that can be invoked from a record trigger flow, screen flow, or any other apex class. This invocable method takes the quote record Id and the email template Id and then queries the most recent quote pdf file related to the quote and sends an email to the customer with the quote pdf attached.
SOLUTION
Step 1
Create an email template using the Quote object.
Email Subject: Quote {!Quote.QuoteNumber} is Ready for Approval
Thank you for choosing ABC Co! Your quote is attached. Please review and double check all records are correct.
If you have any questions or concerns, feel free to reach out.
Regards,
{!Quote.OwnerFullName}
Step 2
Create an apex invocable class and method that can be invoked from the flow.
public with sharing class QuoteEmailNotification {
@InvocableMethod(label='Send Quote Email with PDF' iconName='slds:standard:quotes')
public static void QuoteCreateAndEmail(List < Requests > Reqs) {
for(Requests req : Reqs){
SendEmail(req.recordId, req.EmailTemplateId);
}
}
public static void SendEmail(Id quoteID, String emailTemplateID) {
list<Quote> Quotes = new list<Quote>([SELECT Id, Owner.Name, Owner.Email, ContactId, Contact.Email FROM Quote WHERE ID = :quoteID]);
Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
message.setTargetObjectId(Quotes[0].ContactID);
message.setSenderDisplayName(Quotes[0].Owner.Name);
message.setReplyTo(Quotes[0].Owner.Email);
message.setUseSignature(false);
message.setBccSender(false);
message.setSaveAsActivity(true);
message.setTemplateID(emailTemplateID);
message.setWhatId(quoteID); //This is important for the merge fields in template to work
message.toAddresses = new String[]{Quotes[0].Contact.Email};
//Get and attach the Quote Document
List<QuoteDocument> QDs = new list<QuoteDocument>([SELECT Name, Document, ContentVersionDocumentId FROM QuoteDocument WHERE QuoteId = :quoteID Order By CreatedDate DESC limit 1]);
List<Messaging.EmailFileAttachment> attachments = new List<Messaging.EmailFileAttachment>();
for (QuoteDocument QD: QDs) {
Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
efa.setFileName(QD.Name);
efa.setBody(QD.Document);
attachments.add(efa);
}
message.setFileAttachments(attachments);
Messaging.SingleEmailMessage[] messages = new List < Messaging.SingleEmailMessage > { message };
Messaging.SendEmailResult[] results = Messaging.sendEmail(messages);
if (results[0].success) {
System.debug('The email was sent successfully.');
} else {
System.debug('The email failed to send: '+results[0].errors[0].message);
//You can create a task here, related to the quote or opportunity record, to identify that the email has failed
}
}
public class Requests {
@InvocableVariable(label='Quote Record Id' required=true)
public String recordId;
@InvocableVariable(label='Email Template Id' required=true)
public String EmailTemplateId;
}
}
Step 3
Below is the test class for the above apex code which is required for deployment. You will have to populate the required fields for the account, contact, opportunity, and quote records in the below code as per your salesforce org configuration.
@isTest
public class QuoteEmailNotification_Test {
public static testmethod void QuoteEmailNotificationTest(){
list<Account> Accounts = new list<Account>();
Accounts.add(New Account(Name = 'Test Company'));
insert Accounts;
list<Contact> Contacts = new list<Contact>();
for(Account acc : Accounts){
Contact cont = new Contact();
cont.LastName = 'Waqar Hussain';
cont.AccountId = acc.Id;
cont.Email = '[email protected]';
Contacts.add(cont);
}
insert Contacts;
list<Opportunity> Opps = new list<Opportunity>();
for(Account acc : Accounts){
Opportunity opp = new Opportunity();
opp.Name = acc.Name+'-Opp';
opp.AccountId = acc.Id;
opp.StageName = 'Qualification';
opp.CloseDate = date.today().addDays(30);
Opps.add(opp);
}
insert opps;
list<Quote> Quotes = new list<Quote>();
for(Opportunity opp : Opps){
Quote q = new Quote();
q.OpportunityId = opp.Id;
q.Name = 'Quote-'+opp.Name;
q.Status = 'New';
q.ContactId = Contacts[0].Id;
Quotes.add(q);
}
insert quotes;
string body = 'Testing base 64 encode';
QuoteDocument qdoc = New QuoteDocument();
qdoc.QuoteId = quotes[0].Id;
qdoc.Document = blob.toPdf(body);
insert qdoc;
EmailTemplate e = [SELECT Id FROM EmailTemplate WHERE DeveloperName = 'Quote_Approval_Email'];
list<QuoteEmailNotification.Requests> Requests = new list<QuoteEmailNotification.Requests>();
QuoteEmailNotification.Requests req = new QuoteEmailNotification.Requests();
req.recordId = quotes[0].Id;
req.EmailTemplateId = e.Id;
Requests.add(req);
test.startTest();
QuoteEmailNotification.QuoteCreateAndEmail(Requests);
test.stopTest();
}
}
Step 4
Finally, create a record trigger flow to invoke the above apex class to send an email with the quote pdf when the quote status is updated to Activated.
The invocable method takes two variables
- Quote Id
- Email Template Id
Cheers!