Sitemap

Simulating Salesforce Email Templates in Apex — Mastering renderStoredEmailTemplate Without Sending

7 min readFeb 22, 2026

--

How to render templates, handle the targetObjectId requirement, and send emails when you don’t have a direct recipient record.

Press enter or click to view image in full size

Introduction

If you’ve ever tried to send an email from Apex using a Salesforce template, you’ve probably felt it — that creeping frustration when everything seems to be in place, but the system refuses to cooperate.

You’ve got your template. You’ve got your data. You even know the recipient’s email address. But Salesforce throws up a wall:

“Missing targetObjectId.”

Suddenly, your flow breaks. Your merge fields don’t populate. And you’re stuck, staring at a requirement that doesn’t make sense for your use case. You don’t have a Lead, Contact, or User record — and you shouldn’t need one.

So what do you do?

This blog is for that exact moment. When you’re blocked by Salesforce’s rigid email-sending rules, and you need a way to simulate, render, and review/send your email content without relying on a targetObjectId.

We’ll walk through the method that unlocks this capability — Messaging.renderStoredEmailTemplate — and show you how to use it to regain control over your email logic in Apex. Whether you’re building a custom flow, integrating with external systems, or just trying to preview your template output before sending, this approach is your way forward.

Why Use renderStoredEmailTemplate?

Before we get into the technical details, let’s pause and ask the real question:

Why do developers even need a method like renderStoredEmailTemplate in the first place?

Salesforce gives us powerful email templates, but the moment we try to use them in Apex, we hit a wall of constraints:

  • You must pass a targetObjectId — and it must be a Lead, Contact, or User.
  • You can’t send to arbitrary email addresses using a template.
  • You can’t preview or inspect the rendered content before sending and sending email uses the limit, which is just 15 in scratch and dev orgs, where we mainly play our role.
  • You can’t easily reuse the email content in other channels as raw with merge fields.

These limitations are especially painful when:

  • You’re sending transactional emails to external systems.
  • You’re working with custom objects that don’t relate to standard recipients.
  • You’re building automation that doesn’t involve a Contact, Lead or User.
  • You want to validate merge fields before sending.

This is where renderStoredEmailTemplate(Resource) becomes essential.

It allows you to render the template content in Apex, giving you a fully populated Messaging.SingleEmailMessage object — complete with subject, body, and even attachments if needed. You can then override the recipient, inspect the content, and send the email manually. No targetObjectId required at send time.

In short, this method gives you flexibility, control, and visibility — three things that are often missing when working with email templates in Apex.

Understanding the Method: renderStoredEmailTemplate

Now that we’ve established why this method is so valuable, let’s look at what it actually is and how it works.

Salesforce provides the Messaging.renderStoredEmailTemplate method to render a stored email template in Apex. Unlike other rendering methods that return abstract results or strings, this one gives you a fully populated Messaging.SingleEmailMessage object — ready to inspect, modify, and send.

Method Signatures

There are two versions of the method available:

public static Messaging.SingleEmailMessage renderStoredEmailTemplate(
Id templateId,
Id whoId,
Id whatId
)
public static Messaging.SingleEmailMessage renderStoredEmailTemplate(
Id templateId,
Id whoId,
Id whatId,
Messaging.AttachmentRetrievalOption attachmentRetrievalOption,
Boolean updateTemplateUsage
)

Parameters Explained

  • templateId The ID of the stored email template you want to render. This must be a valid template record in your org.
  • whoId The recipient record — typically a Lead, Contact, or User. This is required if your template uses recipient-specific merge fields like {!Contact.FirstName}. If you don’t have a real recipient, you can use ‘Null’.
  • whatId The related record for merge fields — such as an Opportunity, Case, or any custom object.
  • attachmentRetrievalOption (optional) Determines whether to include attachments from the template.Options include:
  • Messaging.AttachmentRetrievalOption.METADATA_ONLY
  • Messaging.AttachmentRetrievalOption.METADATA_WITH_BODY
  • Messaging.AttachmentRetrievalOption.NONE
  • updateEmailTemplateUsage (optional) If set to true, Salesforce updates the “Last Used” date on the template. This is useful for tracking template usage metrics.

Return Type

The method returns a Messaging.SingleEmailMessage (Resource) object that contains:

  • getSubject() — The rendered subject line.
  • getHtmlBody() — The rendered HTML body.
  • getPlainTextBody() — The plain text version of the email.
  • Any attachments, if requested (Attachments can only be retrieved for the classic email templates).

This object is not automatically sent — it’s yours to inspect, modify, and send manually using Messaging.sendEmail().

Step-by-Step Implementation

Press enter or click to view image in full size

Let’s walk through how to use renderStoredEmailTemplate in Apex with a real-world scenario. We’ll assume you’ve already created an email template — either Classic or Lightning — and you’re ready to render it using Apex.

Scenario: Sending an Opportunity Summary Email

Let’s say you’ve built a template that summarizes key details about an Opportunity. It includes merge fields like:

  • {!Opportunity.Name}
  • {!Opportunity.Amount}
  • {!Opportunity.CloseDate}

Tip: If your template uses recipient-specific fields like {!Contact.FirstName}, you’ll need to pass a valid whoId when rendering.

Step 1: Identify the Merge Field Dependencies

These IDs determine how merge fields are populated:

  • whoId/targetObjectId — The recipient record (Lead, Contact, or User). Required if your template references recipient fields, in our case not required.
  • whatId — The related record (e.g., Opportunity, Case, or custom object). Required if your template references fields from another object.

Step 2: Render the Template

Use the method to render the template and receive a populated Messaging.SingleEmailMessage object:

Id templateId = '00X5g000001XYZ'; // Your template ID
Id whatId = '0065g000002ABC'; // Opportunity Id, which is related record for fields
Id targetObjectId = null; // targetObjectId is not required and actually we don't have one
Messaging.SingleEmailMessage emailMsg = Messaging.renderStoredEmailTemplate(templateId, targetObjectId, whatId);

// You can log or inspect these values for debugging or preview purposes
System.debug('Subject: ' + emailMsg.getSubject());
System.debug('Body: ' + emailMsg.getHtmlBody());
  • getHtmlBody() — The rendered HTML content.
  • getPlainTextBody() — The plain text version.
  • Etc..

Tada! Now, you know your way to use these values! You use this for Inspecting data, Previewing in LWC or sending without targetObjectId.

Choosing Your Method: renderStoredEmailTemplate vs. setTemplateId()

Salesforce provides two main ways to work with stored email templates in Apex. While they may seem interchangeable at first glance, they serve very different purposes. Here is the breakdown of how they behave in the wild:

The “Inspection” Route: renderStoredEmailTemplate

Use this when you need flexibility, visibility, or total control over the content before it leaves the building.

  • The Purpose: It renders the template content so you can inspect it or modify it manually before sending.
  • The Result: It returns a Messaging.SingleEmailMessage object that is populated but not sent.
  • Recipient Rules: You only need a targetObjectId if your template uses specific recipient merge fields. Otherwise, it’s quite relaxed.
  • Why Developers Love It: You can actually “see” the subject, HTML body, and plain text body in your code. Plus, you can send it to any arbitrary email address by manually overriding the setToAddresses() method.
  • Best For: Previews, custom logging, or sending Salesforce template content through external email systems.

The “Direct” Route: setTemplateId()

Use this for standard delivery when you have a valid Salesforce record and just want the email to go out with minimal fuss.

  • The Purpose: It tells Salesforce to grab a template and send it directly during the sendEmail() operation.
  • The Result: There is no return value; the content generation happens internally behind the scenes.
  • Recipient Rules: A targetObjectId (Lead, Contact, or User) is always required. You can’t skip this part.
  • The Catch: You cannot inspect the rendered content. It’s a “fire and forget” method where the final body and subject stay internal to the send operation.
  • Best For: Standard automated notifications to known Salesforce records.

The Quick Decision Matrix

  • Need to preview or log the email content in Apex? Go with renderStoredEmailTemplate.
  • Sending to an external address not saved in a Lead/Contact? Go with renderStoredEmailTemplate.
  • Just want to send a standard alert to a User or Contact? Stick with setTemplateId().
  • Dealing with Attachments? Both support them, but renderStoredEmailTemplate gives you more manual oversight.

Implementation Tips

  • Use a valid templateId referencing a stored email template.
  • Pass a whoId only if the template uses recipient-specific merge fields. If no recipient is available, use null.
  • Provide a whatId if the template references related object fields (e.g., Opportunity, Case, or custom objects).
  • To include attachments from a Classic template, use the extended method signature with specific AttachmentRetrievalOption.
  • For Lightning templates, attachments are stored as ContentDocument files. You can query them through ContentDocumentLink, get the latest published version, and then set them as entity attachments on your email:
// Example: Query files linked to the email template
List<ContentDocumentLink> cdlList = [
SELECT ContentDocument.LatestPublishedVersionId
FROM ContentDocumentLink
WHERE LinkedEntityId = :templateId];

// Collect all version Ids
List<Id> versionIds = new List<Id>();

for (ContentDocumentLink cdl : cdlList) {
versionIds.add(cdl.ContentDocument.LatestPublishedVersionId);
}

// Attach to email
Messaging.SingleEmailMessage emailMsg = Messaging.renderStoredEmailTemplate(templateId, null, whatId);
emailMsg.setEntityAttachments(versionIds);

✅ This way, even Lightning template files can be delivered with your email.

  • Always inspect the rendered content before sending, especially in dynamic or bulk operations.
  • renderStoredEmailTemplate only prepares the SingleEmailMessage. You must still call Messaging.sendEmail() and either set setToAddresses() or setTargetObjectId() to actually deliver it.
  • Do not use the setTemplateId(), for sending email when you don’t have targetObjectId. The template has already been rendered.

Conclusion

Press enter or click to view image in full size

Working with Salesforce email templates in Apex often feels deceptively simple — until you hit the wall of targetObjectId requirements, merge field failures, and rigid sending constraints. For developers who need flexibility, visibility, and control, the standard setTemplateId() approach just doesn’t cut it.

That’s why renderStoredEmailTemplate matters.

It gives you the ability to:

  • Render email content without sending.
  • Populate merge fields using any record — standard or custom.
  • Work around the need for a Lead, Contact, or User.
  • Send emails to arbitrary addresses with full template fidelity.
  • Reuse rendered content across channels or in custom UI components.

Whether you’re building transactional flows, integrating with external systems, or simply trying to validate your templates before delivery, this method is your way forward. It’s not just a workaround — it’s a clean, supported, and powerful solution.

And once you understand how to use it properly, you’re no longer at the mercy of Salesforce’s constraints. You’re in control.

--

--

Kevin Suvagiya
Kevin Suvagiya

Written by Kevin Suvagiya

Salesforce Developer with experience building scalable, high-performance solutions using Apex, LWC, Flows, and Integrations.