Templates
A template defines the content of an email — subject, preheader and body — and the variables that personalise it for each recipient. Templates are versioned: every content edit creates a new, immutable version. A template sends only while it is active, and it can only be activated after its current version has passed content verification. Each template also carries a message class that decides which send paths may use it.
Key fields
Template
| Field | Description |
|---|---|
id / name |
Identifier and display name. |
status |
draft, active or archived. See Statuses. |
current_version |
The version that sends, renders and test sends use. Every content edit increments it. |
message_class |
marketing (the default), transactional or critical. Campaigns and automations require marketing, the transactional endpoints transactional, and OTP critical — see Messages. It can only be changed while the template is draft; otherwise PATCH answers 422 with message_class_locked. |
strict_variables |
When true, a variable referenced in the content but given no value fails the render instead of rendering as empty text. |
Template version
| Field | Description |
|---|---|
version |
The version number, starting at 1. |
document |
The structured content — see Content model. |
format / source |
The same content in the older single-string form: mjml (compiled to responsive HTML) or html (used as is). |
subject / preheader |
Subject line and preview text of the default locale; both can contain variables. |
default_locale |
The locale sends use. |
variables |
The variable names found in the content. |
variable_specs |
Per-variable validation rules — see Variables. |
verify_status / verify_reason |
The stored verification verdict of this version and, when rejected, the flagged categories. See Statuses. |
Statuses
Template status
| Status | Meaning |
|---|---|
draft |
Being edited, or edited since it was last activated. Cannot send: transactional and OTP sends answer 422 template_not_active, a campaign cannot be scheduled or launched with it, and automation send steps skip it. Test sends and previews work. |
active |
The current version is verified and sends. |
archived |
Retired with DELETE /templates/{id}. It cannot be edited, activated again, or used for new sends. A campaign that already launched keeps sending the version it pinned. |
Version verification status
verify_status on a version is the verdict stored by the last Verify run for that content:
| Status | Meaning |
|---|---|
not_verified |
Verify has never been run for this version. |
pending |
No final verdict yet. This is also the answer when the moderation service could not be reached, so treat it as "not approved", not as "queued and will pass". |
approved |
The content passed verification; the version can be activated. |
rejected |
The content failed verification; verify_reason lists the flagged categories. Change the content (which creates a new version) and verify again. |
Lifecycle
POST /templates ──► draft ──Verify (approved) + PATCH status: active──► active
▲ │
└──────── content edit (creates a new version) ────┘
draft / active ──DELETE /templates/{id}──► archived (final)
POST /templatescreates the template indraftwith version 1.POST /templates/{id}/verifywith aversionruns content verification for that version and stores the verdict. It answers withstatus(pending,approvedorrejected),reason, and the per-link results inlinkswhen links were checked.PATCH /templates/{id}withstatus: "active"and no content activates the current version. It is refused with422 VALIDATION_ERRORandvalidation_error: [{ "field": "status", "message": "template_not_verified" }]when the current version has noapprovedverdict, when the verdict could not be read, or when the content changed while the request was running. Amarketingtemplate must also contain the{{unsubscribe_url}}variable in the visible body of the email, or activation is refused with422.- Any
PATCHthat carries content —document,format,source,subject,preheaderorvariable_specs— creates a new version and puts the template back indraft, even if it wasactiveand even if the same request asks forstatus: "active". Sending stops until the new version is verified and activated. APATCHthat changes onlynamekeeps the version and status. DELETE /templates/{id}archives the template. Archiving cannot be undone.
Transactional and OTP sends additionally check the active version’s verdict on every request: a rejected template is 422 content_moderation_rejected, and a verdict that is not ready, or a moderation service that is busy or unreachable, is 409 content_moderation_pending, which you can retry. Campaign launches have their own content checks — see Campaigns.
Content model
A version’s document has schema_version: 1, a default_locale, the list of locales, subject and preheader maps keyed by locale, and blocks. Currently a document consists of exactly one raw_html block, whose props.format is html or mjml and whose props.content holds the markup per locale (at least for the default locale). A value missing for a locale falls back to the default locale.
Sends always use the default locale. POST /templates/{id}/render and POST /templates/preview-source accept a locale to preview another one.
Variables
Templates use {{ name }} placeholders. These names are always available:
| Variable | Value |
|---|---|
{{first_name}}, {{last_name}}, {{email}} |
From the recipient’s contact record. |
{{custom.<key>}} |
A custom field value of the contact. |
{{unsubscribe_url}} |
The recipient’s signed unsubscribe link; added to marketing mail only. |
Any other name is filled from the variables of a transactional or OTP request, the data of a render or test send, or the mapped columns of a campaign recipient upload. A variable with no value renders as empty text, unless strict_variables is true.
Variable validation
variable_specs maps a variable name to a kind. Values you supply are validated against it, and a value that fails is refused with invalid_variable (422, or a failed item in a batch). A variable without a spec is treated as short_text.
kind |
Accepted values |
|---|---|
short_text |
Up to max_length characters (default 128); no line breaks or control characters, no HTML tags, no http:// or https:// URLs. |
free_text |
Longer prose; no HTML or Markdown markup, no links, URLs, domain names, mailto: or tel:, and no template expressions. |
code |
1–64 characters of A–Z a–z 0–9 . _ -, for example a discount or verification code. |
id |
The same pattern as code, for identifiers. |
numeric |
1–64 digits. |
url |
An absolute http or https URL whose host is listed in url_allowlist; with an empty allowlist every URL is refused. |
Values must be strings, numbers or booleans.
Rendering and preview
POST /templates/{id}/renderrenders a version (the current one by default) with an explicitdataobject or acontact_id, and returnshtml,textandsubject.POST /templates/preview-sourcerenders unsaved content the same way, so you can preview while editing.GET /templates/{id}/versionslists all versions with their verification status.
Test sends
POST /templates/{id}/test-send sends the current version to up to 10 addresses. It is a real send:
- It works in any template status, but checks the supplied variables and the content verdict first (
422 invalid_variable,422 content_moderation_rejected,409 content_moderation_pending). - It goes out from a verified domain; pass
domain_idwhen your account has more than one. The From address comes from the route forblastfor a marketing template and fortransactionalotherwise — see Sender profiles. - Suppressed addresses are skipped. Every other address gets its own message, with the template’s message class and
source_type: null. When you do not passcontact_id, an address that is not yet a contact is added to your contacts. - Each message costs one credit, and test sends are not held back by credit-protection minimums.
Link verification
Verify also checks the links in the content. GET /templates/{id}/versions/{version}/links returns what the last Verify stored for that version, without checking again:
| Field | Description |
|---|---|
status |
The stored result for the link set: not_verified (no run stored), pending, approved or rejected. A run that found no links is approved with extracted_count: 0. |
reason |
Why the link set was rejected; null otherwise. |
links[] |
One entry per link: url, status (pending, approved or rejected), stage and reason. |
stage |
How far the check of a link got: skipped (allowlisted or not a web link; counts as approved), reputation, content, or not_checked (the run ended before reaching it). |
extracted_count |
How many links the run found. |
links_truncated |
true when the stored list is shorter than the run’s full set. |
links_set_truncated_by |
Present when the run was cut short: deadline or rejected. Links it did not reach stay not_checked. |
Stored verdicts do not expire and never change on their own; run Verify again to refresh them. Treat this endpoint as a review surface: fix rejected links before activating.
In marketing mail, links are rewritten so clicks are recorded, and an open pixel is added; these produce the open and click message events and the email.opened and email.clicked webhooks.
Related API
- Templates tag —
GET /templates,POST /templates,GET /templates/{id},PATCH /templates/{id},DELETE /templates/{id},GET /templates/{id}/versions,GET /templates/{id}/versions/{version}/links,POST /templates/{id}/render,POST /templates/preview-source,POST /templates/{id}/verify,POST /templates/{id}/test-send. - Assets tag —
GET /assets,POST /assets/presign,POST /assets/{id}/confirm,DELETE /assets/{id}, for images used in templates; see Upload an image asset.
See the API Reference for full schemas, and Send your first campaign for a worked example.