Back in 2018, I published the most visited post on my blog about sending Salesforce emails with attachments via Apex.
Just recently, I had a conversation with the company about a use case I believe is widespread.
In Salesforce, you can render Visualforce content as a PDF, which is very convenient for generating dynamic files such as certificates and invoices without relying on third-party services.
For example, given the use case where you use Visualforce to generate and send invoices to your customers.
One option is to send these attachments as hotlinks to publicly available pages, so customers can click the link to view the generated invoice content.
However, this approach has numerous issues.
The apparent issue is the user experience.
Another issue is that the system generates the PDF at runtime, which itself has a few problems, such as wasting computing resources on every visit and, most importantly, the risk of content being updated on record changes.
The biggest problem is the security risk of exposing invoice information since the documents are public.
The ideal scenario is to generate a snapshot of the document, attach it to the email as a file, and save it in the related record's files once.
It motivated me to write this post and share the example of how you can do precisely that.
Visualforce Implementation
Let’s assume a relatively straightforward case to generate a certificate and attach it to the email.
I won’t describe in detail how to create a Visualforce page.
The most important thing is to add the attribute to render it as a PDF.
You can use query parameters to fetch the record data.
The most straightforward approach is to use the Standard Controller, which automatically returns the record based on the specified id as a query parameter.
Tip: If you need to generate a certificate, you can render the background image as a static resource and place the dynamic content on top using position: absolute, which is helpful for this use case.
<apex:page standardController="Account" renderAs="pdf" applyHtmlTag="false" applyBodyTag="false" showHeader="false" standardStylesheets="false">
<!-- Place your markup -->
</apex:page>
Apex Class Implementation
Given that you have the Visualforce page, Apex provides a convenient method to render the page content as a PDF.
The method returns a blob, which is essentially binary file data.
Meaning, you can use it the same way as Attachments and Content Versions.
However, before doing that, you need to update the Email Attachment settings in the organization.
It has three options:
- Send attachments always as links.
- Send attachments as files up to 3MB and as links if they exceed that size.
- Send attachments as files up to the Salesforce limit and as links if more.
There are a few nuances to note:
- Attachments as links are the publicly accessible share links to the Salesforce Files.
- Shared links do not expire in Lightning Experience, but you can manually expire them.
- The per-file attachment limit in Salesforce is 25 MB.
You can add an email file attachment using the SingleEmailMessage class via the setFileAttachments method.
Its EmailFileAttachment API is straightforward. You only need to set the file name, file data, and content type.
Note: PDF has a content type of
application/pdf
Regarding the SingleEmailMessage itself, you need to either provide static content or fetch the data from the email template, and set the organization-wide email address as the sender.
A few words about its limitations.
- It has a limit of 5000 recipients per day per org.
- Each ‘To’, ‘CC’, and ‘BCC’ field counts against the limits.
- Emails sent using
setTargetObjectId()set against the User object do not count against the limits.
Here is an example code snippet for reading content as a PDF and sending it as an email attachment.
Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
message.setToAddresses(new String[] { 'nikita@digitalflask.com' });
List<Messaging.EmailFileAttachment> attachments = new List<Messaging.EmailFileAttachment>();
// Get PDF content by referencing the page.
// Use your Visualforce page here.
PageReference pdfPage = Page.Certificate;
pdfPage.getParameters().put('id', 'YOUR_ACCOUNT_ID');
Blob pdfBlob;
// In test context, create a dummy blob
if (Test.isRunningTest()) {
pdfBlob = Blob.valueOf('Test PDF content');
} else {
pdfBlob = pdfPage.getContentAsPDF();
}
Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
efa.setFileName('Certificate_2026.pdf');
efa.setBody(pdfBlob);
efa.setContentType('application/pdf');
attachments.add(efa);
message.setFileAttachments(attachments);
message.setSubject('Certificate');
message.setHtmlBody('Hello, here is your certificate!');
List<OrgWideEmailAddress> addresses = [SELECT Id FROM OrgWideEmailAddress];
if (!addresses.isEmpty()) {
message.setOrgWideEmailAddressId(addresses[0].Id);
}
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { message });
Please note that I’ve used static text in the email message, but you can always fetch a specific email template and use it instead.
EmailTemplate template = [SELECT Id, Subject, HtmlValue FROM EmailTemplate WHERE DeveloperName = 'YOU_EMAIL_TEMPLATE'];
message.setTemplateId(template.Id);
message.setSubject(template.Subject);
message.setHtmlBody(template.HtmlValue);
Save PDF to Salesforce Files
As a bonus, you can also save a copy of the generated file to the Account record.
It’s a good practice because Salesforce users can always access the generated files in the system.
For example, if you have a customer or a partner portal built with Experience Cloud, you can generate a certificate for an account and make it accessible in the community.
Salesforce Files are represented as Content Versions in the database, and the Content Document Link controls access to them.
Therefore, you need to insert these records.
The API is similar to Email Attachments and requires the file data, title, and linked entity ID, among other things.
In our case, we will use Account ID as the linked entity, so that the files appear in its related list.
Note: Linked entity ID can be a user ID, which is convenient when you want to share files directly with external users.
// Given that you have `pdfBlob` from the previous step.
String title = 'Certificate_' + Date.today().year();
ContentVersion cv = new ContentVersion();
cv.VersionData = pdfBlob;
cv.Title = title;
cv.PathOnClient = title + '.pdf';
cv.IsMajorVersion = true;
insert cv;
// Get the ContentDocumentId
ContentVersion insertedVersion = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id LIMIT 1];
// Create ContentDocumentLink to Account
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = insertedVersion.ContentDocumentId;
cdl.LinkedEntityId = 'YOUR_ACCOUNT_ID';
cdl.ShareType = 'V'; // Viewer permission
cdl.Visibility = 'AllUsers';
insert cdl;
Sometimes the document needs to be updated, e.g., it had a typo on the certificate, and the Salesforce admin needs to regenerate the PDF.
In this case, instead of inserting a new document, you can check whether the document already exists.
For example, by checking the name, if you have a defined naming convention such as "Certificate_" + year.
If there is an existing document, insert a new Content Version of the same document using its ContentDocumentId.
cv.ContentDocumentId = 'YOUR_EXISTING_CONTENT_DOCUMENT_ID';
Conclusion
In this post, I outlined a typical scenario of generating the PDF documents, sending them as email attachments, and saving a copy in Salesforce Files.
It can be valuable across many implementations, as you can embed this business logic in Apex Triggers, Flow automations, and as a standalone action.
For complex scenarios, consider third-party applications, as they usually offer better tooling and flexibility.
However, for small implementations with few requirements, such as static certificates, it could be the best option.
Have you tried a similar approach? Let me know how it worked for you or if you faced any challenges.

Nikita Verkhoshintcev
Senior Salesforce Technical Architect & Developer
I'm a senior Salesforce technical architect and developer, specializing in Experience Cloud, managed packages, and custom implementations with AWS and Heroku. I have extensive front-end engineering experience and have worked as an independent contractor since 2016. My goal is to build highly interactive, efficient, and reliable systems within the Salesforce platform. Typically, companies contact me when a complex implementation is required. I'm always open to collaboration, so please don't hesitate to reach out!
