People working on laptops

Send Email with Attachments in Salesforce Apex

by Nikita Verkhoshintcev

No doubt, Salesforce is an excellent platform with endless possibilities. It allows you to set up workflows, automated email out, and much more.

In this article, I would like to talk about one specific real-world scenario. Recently, we have been working on the investment platform build on top of Salesforce for one of our clients.

At some point, we had an idea of collecting leads by sending out the investor materials, such as company marketing presentations for an exchange to their contact information.

This task required us to perform the following actions:

  1. Create the Lead object if he does not exist yet
  2. Query all the specified attachments and send them via email to the Lead to make sure that the email is correct
  3. Save sent email as the Lead activity for the sales team
  4. Subscribe Lead to the MailChimp list if he checked the GDPR field
  5. Remember Lead's browser so that he could download attachments without filling the form in future to improve the user experience

Of course, this is quite a lot of things to do to make the life of marketers and sales team easier and automate the process. But I would like to focus on the second step and show how you could send out the email with dynamic attachments from the Apex class.

TL;DR;

Please, find the final version of the code at the end of the post.

Single Email Message

Let's pretend that we have an Apex controller for downloading attachments. It could be the REST API endpoint or just the regular class for the sake of simplicity.

global class DownloadAttachments {
/**
 * @param {String} startupId The Id of the target startup object, which files we should attach to the email
 * @param {String} emailAddress The email address of the recipient
 */
  global static void download(String startupId, String emailAddress) {
    ...
  }
}

In this case, to send the email, we will use the Messaging.SingleEmailMessage class.

It has the method called setFileAttachments which we will use attach the files we want to send along.

This method accepts the list of Messaging.EmailFileAttachment objects.

Finally, each of this attachment objects should have the name, body, and content type to correctly display it in the email.

To begin with, let's create the new instance of the SingleEmailMessage class and set the recipient address.

Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();

message.setToAddresses(new String[] { emailAddress });

Then, the exciting part begins. Salesforce allows us to do a lot of things with the messaging class.

Email File Attachments

Before jumping into the mailer class, let's query all the required information from the object's attachments.

Note: We can use inner joins in queries to get the list of attachments straight away, but Salesforce does not allow us to retrieve Body file data inside them.

Therefore, we need to perform two queries. First of all, we will get the list of attachment Ids and then retrieve the rest separately.

List<Startup__c> startups = [SELECT Id, (SELECT Id FROM Attachments) FROM Startup__c WHERE Id = :startupId];
if (!startups.isEmpty()) {
  Set<Id> attachmentIds = (new Map<Id, SObject>(startups[0])).keySet();
  List<Attachment> files = [SELECT Name, Body, ContentType FROM Attachment WHERE Id IN :attachmentIds];
}

As the result of the code above, we would get the list of the files with all the information we need to proceed further.

Now, we need to generate the list of the actual email attachments out of the files that we have and attach them to the email.

List<Messaging.EmailFileAttachment> attachments = new List<Messaging.EmailFileAttachment>();
for (Attachment file: files) {
  Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
  efa.setFileName(file.Name);
  efa.setBody(file.Body);
  efa.setContentType(file.ContentType);
  attachments.add(efa);
}
message.setFileAttachments(attachments);

You can configure whenever you want to attach files as links or actual data in Setup > Email > Email Attachments.

Salesforce Setup - Email - Email Attachment settings

Here we have the attachment list ready. Next, we can continue with the single email message.

Email Templates

We can compose the email content both in the plain text and HTML right in the Apex class.

However, that is not the best practice.

We want to keep it clear and dynamic. We do not have to edit and deploy our code on every text change. Plus, we might want to customize the email so that it will follow the company branding style guides.

Here come email templates for rescue.

In Salesforce, you can build own email templates which are very versatile. To do so, go to the Setup > Email > Classic Email Templates and click New Template. Choose Custom (without using Letterhead), select folder, check the Available For Use box, give it a name and subject.

Salesforce Setup - Email - Email Templates settings

Then provide the HTML and plain text content.

In conclusion, we have the email template ready to use. We can query it by the DeveloperName and then set its Id, Subject, and HtmlValue to our message.

For instance, you might query templates dynamically based on the Lead language to send out personalized emails.

EmailTemplate template = \[SELECT Id, Subject, HtmlValue FROM EmailTemplate WHERE DeveloperName = 'InvestorMaterial'\];
message.setTemplateId(template.Id);
message.setSubject(template.Subject);
message.setHtmlBody(template.HtmlValue);

As a result, the code above will apply the custom template to the email we are planning to send.

Organization-Wide Email Addresses

Besides the custom templates, Salesforce provides the functionality to set the sender email address as well via the organization-wide email address settings.

Otherwise, the email will have the System Administrator user's address by default.

In addition to templates, organization-wide address settings give us the opportunity to customize the message further.

Go to Setup > Email > Organization-Wide Addresses and click New.

Enter the display name, email address, and choose which Profiles could use this address.

Salesforce Setup - Email - Organization-Wide Addresses settings

Next, we can query this address and set is as the sender.

List<OrgWideEmailAddress> addresses = \[SELECT Id FROM OrgWideEmailAddress WHERE Address = 'info@digitalflask.com'\];
if (!addresses.isEmpty()) {
  message.setOrgWideEmailAddressId(addresses\[0\].Id);
}

If you know the Lead or Contact Id, you can record the email out as the object's activity like

message. setTargetObjectId(lead.Id); message.setSaveAsActivity(true);

Send Email

After all, the only part left is actually to try to send the message.

try {
  Messaging.sendEmail(new Messaging.SingleEmailMessage\[\] { message });
} catch (Exception e) {
  throw e;
}

If you execute the method via the REST API, Throw will return the 500 Http server error response with the detailed description so that we could handle it on the client side.

In summary, I hope that I helped you learn more about the Salesforce email settings and how to you could use the Messaging class to extend and customize the standard functionality to send out dynamically generated and personalized emails.

In conclusion, here is the complete sample of the messaging method.

global class DownloadAttachments {
/**
  * Send out the startup object attachments to the specified email and save it as the activity
  * @param {String} startupId The Id of the target startup object, which files we should attach to the email
  * @param {String} emailAddress The email address of the recipient
  */
  global static void download(String startupId, String emailAddress) {
    Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
    // Set the recipient address
    message.setToAddresses(new String[] { emailAddress });
    // Attach files to the message
    List<Startup__c> startups = [SELECT Id, (SELECT Id FROM Attachments) FROM Startup__c WHERE Id = :startupId];
    if (!startups.isEmpty()) {
      Set<Id> attachmentIds = (new Map<Id, SObject>(startups[0])).keySet();
      List<Attachment> files = [SELECT Name, Body, ContentType FROM Attachment WHERE Id IN :attachmentIds];
    }
    List<Messaging.EmailFileAttachment> attachments = new List<Messaging.EmailFileAttachment>();
    for (Attachment file: files) {
      Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
      efa.setFileName(file.Name);
      efa.setBody(file.Body);
      efa.setContentType(file.ContentType);
      attachments.add(efa);
    }
    message.setFileAttachments(attachments);
    // Set the message template
    List<EmailTemplate> templates = [SELECT Id, Subject, HtmlValue FROM EmailTemplate WHERE DeveloperName = 'InvestorMaterial'];
    if (!templates.isEmpty()) {
      message.setTemplateId(template[0].Id);
      message.setSubject(template[0].Subject);
      message.setHtmlBody(template[0].HtmlValue);
    }
    // Set the message sender address
    List<OrgWideEmailAddress> addresses = [SELECT Id FROM OrgWideEmailAddress WHERE Address = 'info@digitalflask.com'];
    if (!addresses.isEmpty()) {
      message.setOrgWideEmailAddressId(addresses[0].Id);
    }
    // Save the message as activity
    List<Lead> leads = [SELECT Id FROM Lead WHERE Email = :emailAddress];
    if (!leads.isEmpty()) {
      message. setTargetObjectId(leads[0].Id);
      message.setSaveAsActivity(true);
    }
    // Send the message
    try {
      Messaging.sendEmail(new Messaging.SingleEmailMessage[] { message });
    } catch (Exception e) {
      throw e;
    }
  }
}
Nikita Verkhoshintcev photo

Nikita Verkhoshintcev

Salesforce Consultant

Senior Salesforce and full-stack web developer. I design and build modern Salesforce, React, and Angular applications for enterprises. Usually, companies hire me when complex implementation, custom development and UI is required.

Let's work together!

Contact us today and take your digital end-user experience to the next level.

Contact us