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)
  1. POST /templates creates the template in draft with version 1.
  2. POST /templates/{id}/verify with a version runs content verification for that version and stores the verdict. It answers with status (pending, approved or rejected), reason, and the per-link results in links when links were checked.
  3. PATCH /templates/{id} with status: "active" and no content activates the current version. It is refused with 422 VALIDATION_ERROR and validation_error: [{ "field": "status", "message": "template_not_verified" }] when the current version has no approved verdict, when the verdict could not be read, or when the content changed while the request was running. A marketing template must also contain the {{unsubscribe_url}} variable in the visible body of the email, or activation is refused with 422.
  4. Any PATCH that carries content — document, format, source, subject, preheader or variable_specs — creates a new version and puts the template back in draft, even if it was active and even if the same request asks for status: "active". Sending stops until the new version is verified and activated. A PATCH that changes only name keeps the version and status.
  5. 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}/render renders a version (the current one by default) with an explicit data object or a contact_id, and returns html, text and subject.
  • POST /templates/preview-source renders unsaved content the same way, so you can preview while editing.
  • GET /templates/{id}/versions lists 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_id when your account has more than one. The From address comes from the route for blast for a marketing template and for transactional otherwise — 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 pass contact_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.

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.

  • 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.