Managing Email Templates with the REST API
Email templates and their per-language versions are ordinary platform entities, so they are managed through the standard REST API v3 entity endpoints. A template holds the source-language Subject and Body; each translation is a child record.
There is no send-email endpoint in the REST API. The API writes template data; sends are triggered by automations, or by the Send Test action in the Setup UI. For how a template is localized when it is actually sent, see Email Template Localization at Send Time.
Entities
| Entity | Key prefix | Role |
|---|
EmailTemplate | 7OD | The template. Its Subject and Body are the source language. |
EmailTemplateLanguage | 7PW | One translation. Master-detail child of EmailTemplate; reparenting is not allowed. |
There is no child record for the source language - the parent is the source.
EmailTemplate fields
| Field | Type | Constraints |
|---|
Name | string | Required, 120 characters. Not enforced unique. |
Subject | string | Required, 255 characters. Supports merge tokens. |
Body | string | Multiline formula expression. No length cap. |
Type | picklist | Text | HTML. Defaults to Text. Not updateable after create. |
IsActive | bool | The "Ready For Use" flag. |
Description | string | 512 characters. |
ReplyTo | string | 512 characters, validated as an email address. |
FullBranding | bool | HTML branding mode. The HTML-only restriction is applied by the template editor, not the entity layer — a direct REST write is not gated on it. |
Tags | string | 255 characters. Semicolon-separated; normalized on write to 50 characters per tag. |
IsStandard | bool | System-template flag. Host-writable only; non-host callers cannot delete a record where this is true. |
EmailTemplateLanguages | collection | Accepted as an embedded array on create. Not a stored column. |
EmailTemplateLanguage fields
| Field | Type | Constraints |
|---|
EmailTemplateId | string | Required. Master-detail parent. |
Language | string | Required. Must be an active language on the portal, matched case-insensitively. |
Subject | string | Required, 255 characters. |
Body | string | 64,000 characters, multiline. |
Endpoints
All standard entity operations apply. Entity names in the path are case-insensitive.
| Method | Route | Purpose |
|---|
GET | /api/3.0/entity/emailtemplate/{id} | Read one template |
GET | /api/3.0/entity/emailtemplate | List templates |
POST | /api/3.0/query | MEQL query |
POST | /api/3.0/entity/emailtemplate | Create - single object or an array for bulk |
PATCH | /api/3.0/entity/emailtemplate/{id} | Partial update |
PUT | /api/3.0/entity/emailtemplate?uniqueField={field} | Upsert by a unique field |
DELETE | /api/3.0/entity/emailtemplate/{id} | Delete - cascades to child translations |
The same operations are available on /api/3.0/entity/emailtemplatelanguage/... for translation records.
Creating a Template with Translations
Translations can be supplied inline on create, as an embedded array:
POST /api/3.0/entity/emailtemplate
Content-Type: application/json
{
"Name": "Welcome Email",
"Type": "HTML",
"Subject": "Welcome, {!Contact.FirstName}",
"Body": "<p>Hello {!Contact.FirstName}</p>",
"IsActive": true,
"EmailTemplateLanguages": [
{ "Language": "fr", "Subject": "Bienvenue", "Body": "<p>Bonjour</p>" },
{ "Language": "de", "Subject": "Willkommen", "Body": "<p>Hallo</p>" }
]
}
This is not atomic. The child translations are written after the parent has already been committed, outside a shared transaction. If a child fails validation, the parent still exists. Verify the children after a create, and be prepared to retry just the failed rows.
Querying templates
POST /api/3.0/query
SELECT Id, Name, Type, IsActive FROM EmailTemplate WHERE IsActive = true
MEQL string literals use double quotes. See MEQL Reference.
Validation
| Rule | Message |
|---|
| Translation language must be active on the portal | Unsupported language code. |
| One translation per template per language | A localized version already exists for this language. |
Type cannot change after create | The template type cannot be changed after creation. |
Type must be a recognized value | Type must be 'Text' or 'HTML'. |
Two write-time transformations happen silently and are worth knowing about:
- HTML bodies are sanitized. Script, iframe, frameset and input elements and a fixed set of
on* event-handler attributes are stripped; iframes are restricted to a host whitelist. This applies to the template and to every translation. Content you post back may therefore differ from what you sent. - Tags are normalized. Split on semicolons, trimmed, first character capitalized, de-duplicated case-insensitively, and truncated to 50 characters per tag.
Permissions
- Standard per-entity
Read / Create / Edit / Delete permission on EmailTemplate is enforced inside every action. Host users bypass the checks. - Customer and Partner role types are denied outright on
EmailTemplate - View, Create, Edit and Delete are all None. A token belonging to a portal user cannot read or write templates at all. - Non-host callers cannot delete a template where
IsStandard is true. - Deleting a template cascades to all of its translations with no per-child confirmation. There is no soft delete.
Behavior to Design Around
Type is immutable. To change a template from Text to HTML, create a new record.Name is not unique, but package deployment matches templates by name. Avoid duplicate names in any org you deploy to or from.- No versioning. There is no version field or history table for templates or translations, and no staleness flag when a parent changes after its translations were written. If you need to detect drift, store your own hash of the source at translation time.
- Translations are not exposed to the chatbot tool surface. The
UpsertEmailTemplate MCP tool and the generic entity tools operate on the parent EmailTemplate only, and AI-authored drafts are always written in the source language.
See More
Email Template Localization at Send Time >>