Skip to main contentSkip to Content
Templates

Templates

Templates are reusable message designs for email, push, Telegram, and on-site channels.

Email types: marketing and transactional

Every email template is either marketing or transactional (category). New templates are marketing unless you choose otherwise: in the editor’s Email type field, in the Email type column of the templates list (which can also filter by type), from a Send Email step in an automation, or through the API.

Marketing (MARKETING, default)Transactional (TRANSACTIONAL)
What it is forNewsletters, onboarding and nurture series, promotions, discounts, win-backPassword resets, receipts, account and security notices, replies to the contact
How often a contact gets itOnce. A contact who already received the template is skipped (Marketing template already sent to this contact)As often as it is triggered
Contact opted out of emailNot sent. The contact needs EMAIL consent OPTED_INSent anyway
Frequency caps and quiet hoursApplied (workspace and campaign)Not applied
Suppression listsAppliedApplied

“Already received” counts messages for that template whose status is SENDING, SENT, DELIVERED, OPENED, CLICKED or BOUNCED; failed and skipped attempts do not count, so a skipped marketing email can still go out later.

The template’s category decides. A campaign of type TRANSACTIONAL only matters for emails sent without a template (inline subject and body) and for other channels. Replies from the Inbox are always transactional.

Keep the category honest: an email that sells something (a discount, an upgrade, a “come back” reminder) is marketing even when an event triggers it.

List Templates

GET /api/workspaces/{workspaceId}/templates

Returns all templates ordered by last updated.

Auth: Session required

Response:

[ { "id": "tpl_abc123", "name": "Welcome Email", "channel": "EMAIL", "category": "MARKETING", "subject": "Welcome to {{workspace.name}}!", "body": "<h1>Hello {{contact.firstName}}</h1>...", "design": { ... }, "createdAt": "2026-01-15T10:00:00.000Z", "updatedAt": "2026-01-20T14:00:00.000Z" } ]

Create Template

POST /api/workspaces/{workspaceId}/templates

Auth: Session required

Request Body:

{ "name": "Welcome Email", "channel": "EMAIL", "category": "MARKETING", "subject": "Welcome to {{workspace.name}}!", "body": "<h1>Hello {{contact.firstName}}</h1><p>Thanks for joining!</p>", "design": { "editor": "plate", "blocks": [...] } }
FieldTypeRequiredDescription
namestringYesTemplate name
channelstringYesTarget channel
categorystringNoMARKETING (default) or TRANSACTIONAL; email templates only, see Email types. Can be changed with PATCH
subjectstringNoEmail subject line
bodyanyYesTemplate content (HTML or structured)
designobjectNoRich editor design data (Plate AI)

Send a Test Email

POST /api/workspaces/{workspaceId}/templates/{templateId}/send-test

Sends the rendered email template once, with [TEST] prefixed to the subject. Available from the editor header and the templates list.

Auth: Session required (MEMBER or above)

Request Body:

{ "contactId": "clx...", "email": "me@example.com", "locale": "pt-BR" }
FieldTypeRequiredDescription
contactIdstringOne of the twoRenders with this contact’s data and language
emailstringOne of the twoAddress to send to; overrides the contact’s email when both are given. Without a contact, sample data (Test User) is used
localestringNoTranslation to send; defaults to the contact’s locale, then the template’s default
sampleDataobjectNoExtra or overriding token values

Response: { "sent": true, "to": "me@example.com" }

Errors: 400 — neither field given, the contact has no email, or the template is not an email template · 502 — the provider rejected the send

Personalization Tokens

Templates use {{path}} tokens resolved against the render context. Paths are dot-separated.

TokenDescription
{{contact.firstName}}Contact’s first name
{{contact.lastName}}Contact’s last name
{{contact.fullName}}First and last name joined
{{contact.email}}Contact’s email
{{contact.phone}}Contact’s phone
{{contact.locale}}Contact’s locale
{{contact.<attribute>}}Custom attribute value (attributes are merged into contact)
{{workspace.name}}Workspace name
{{workspace.slug}}Workspace slug
{{campaign.id}}Campaign ID, when sent from a campaign
{{unsubscribeUrl}}Auto-generated unsubscribe link
{{preferencesUrl}}Preference center link
{{dwc:name}}Dynamic content block by name

Filters

Append | filter to a token: {{contact.createdAt | date}}. Available filters: date, datetime, relative, uppercase, lowercase, capitalize.

Conditionals

{{#if contact.plan == "pro"}}Thanks for being a Pro member!{{#else}}Upgrade today.{{/if}}

Supported comparisons: ==, !=, >, <, or a bare path for truthiness.

Languages

A template has a defaultLocale and any number of translations, each with its own locale. Any real language tag is accepted (pt, pt-BR, es-MX, zh-Hant-TW): the language must be an ISO 639 code and the region an ISO 3166 country, and the tag is stored normalised (pt_br becomes pt-BR). In the dashboard, Add Language is a text field with suggestions, not a fixed list.

Language fallback

Selecting a template in a campaign selects the template and all of its translations. Each contact then receives the closest language available:

  1. The contact’s exact locale (en-CA).
  2. The same language without the region (en).
  3. Another regional variant of that language (en-GB when the contact is en-CA).
  4. The same three steps for the workspace default language.
  5. The template’s default language.

A contact with no locale uses the workspace default. When the campaign setting missing locale behavior is skip, a contact whose language has no match is skipped instead of receiving the default. Every message records the language it was sent in, shown in the message detail and used by A/B statistics.

Template A/B tests

A template can carry its own variants, tested across every campaign that sends it. Open a template and click A/B test.

  • Each variant has a name, a traffic share, an optional subject and an optional HTML body. Empty fields fall back to the template (or the translation) for that language.
  • A variant’s subject can be written per language (the Subject per language section of each variant). The sender uses the subject in the recipient’s language, else the variant’s general subject, else the translated template subject. This is how one email can test ten subject lines across twenty-five languages.
  • Languages scope a variant. With none selected the variant applies to everyone; selecting Portuguese means only contacts resolved to Portuguese can receive it, and the others split between the remaining variants.
  • Each contact is assigned by a stable hash of contact, template and language, so the same person keeps the same variant.
  • Results show recipients, unique opens and clicks, rates, lift against the first variant, confidence from a two-proportion z-test, and a per-language breakdown.
  • Declare winner sends the winning variant to everyone from that point on, within its language scope; Reset splits traffic again.

Template variants and the A/B split node in a campaign are independent: the node decides which branch a contact takes, and the template variant decides which wording that branch sends.

API

  • GET /api/workspaces/{workspaceId}/templates/{templateId}/ab returns the variants with their statistics.
  • PUT the same path with { enabled, metric, variants: [{ id?, name, weight, locales, subject?, subjectByLocale?, body? }] } saves them; variants left out are deleted. subjectByLocale is a map such as { "pt-BR": "…", "it": "…" }; leaving it out keeps the stored subjects.
  • POST /api/workspaces/{workspaceId}/templates/{templateId}/ab/winner with { "variantId": "…" } (or null) declares or clears the winner.
Last updated on