Templates

Keep subjects and bodies in COSS, versioned and localized, and send them with variables.

A template holds an email's subject, HTML and text, in every language you send, with typed variables. You edit its draft, publish it as a numbered version, and send it by id or alias with the values for its variables. Changing your copy no longer needs a deploy, and every email records which template, version and locale it was rendered from.

Creating a template

Create templates in the dashboard (Templates, under the environment's email pages), where the editor previews the draft as you type and sends tests to members of your organization, or through the API:

const { : ,  } = await ...({
  : "Welcome",
  : "welcome",
  : [
    { : "first_name", : "string" },
    { : "plan", : "string", : "Free" },
  ],
  : [
    {
      : "en",
      : "Welcome to Acme, {{first_name}}",
      : "<p>Hi {{first_name}}, you're on the {{plan}} plan.</p>",
      : "Hi {{first_name}}, you're on the {{plan}} plan.",
    },
    {
      : "de",
      : "Willkommen bei Acme, {{first_name}}",
      : "<p>Hallo {{first_name}}, du nutzt den Tarif {{plan}}.</p>",
    },
  ],
});

// Sends use it once the draft is published.
await ...("welcome");

Templates belong to the API key's environment. Give a template the same alias in each environment (for example welcome in development and production) and your code sends it the same way everywhere.

Sending

Pass template_id (the template's id or alias) and variables instead of subject, html and text. Everything else (to, scheduled_at, attachments, tracking, batches, idempotency) works as for any send:

const { : ,  } = await ..({
  : "Acme <onboarding@acme.com>",
  : "ada@example.com",
  : "welcome",
  : "de-AT",
  : { : "Ada" },
});

.(?.); // "Willkommen bei Acme, Ada"
.(?.); // { id: "etpl_…", version: 1, locale: "de" }
  • The template is rendered when the email is accepted: the email stores the result, and later publishes never change it (scheduled emails included).
  • Without template_version, the latest published version is sent. Pin one with template_version: 3.
  • locale falls back from an exact match (de-AT) to its language (de), then to the version's default locale.
  • Variables are checked against their declarations. A missing required one, an unknown name or a value of the wrong type refuses the send with invalid_template_variables, and issues lists each problem (variables.first_name). A template that doesn't exist (template_not_found) or was never published (template_not_published) refuses it too, with HTTP 422.

Template syntax

Nothing in a template runs code. A template can't be published while it has syntax errors or uses a name that isn't a declared variable (or, inside {{#each}}, a field of the list); the editor and the draft's problems show where, by line and column. A rendered subject must still be a valid subject (one line, at most 998 characters).

Variables

Each variable has a name (letters, digits and _), a type and whether it is required (the default):

A default (not for lists) is used when a send leaves the variable out. A send's variables can be at most 64 KB, and a list at most 1,000 items:

await ...("receipt", {
  : [
    {
      : "items",
      : "list",
      : [
        { : "name", : "string" },
        { : "price", : "number" },
      ],
    },
  ],
  : [
    {
      : "en",
      : "Your receipt",
      : "<ul>{{#each items}}<li>{{name}}: ${{price}}</li>{{/each}}</ul>",
    },
  ],
});

Drafts, versions and locales

  • Draft: the one editable copy (getDraft, updateDraft). Saving replaces it whole. Every save increases its revision; pass the revision you read to updateDraft and publish to be refused (template_revision_mismatch) instead of overwriting someone else's changes. The SDK never retries a save on its own (a repeat of one that went through would be refused): read the draft again before trying once more.
  • Versions: publish copies the draft into the next version (1, 2, 3…), which never changes. Publishing a draft without changes returns the current version, so a publish with the revision you reviewed is safe to retry (the SDK retries only those). List them with versions.list, and bring an old one back with versions.restore (it replaces the draft; publish it to send it again).
  • Locales: each version holds one subject, HTML and text per locale (up to 30), published together, with one default locale. Locales are BCP 47 tags such as en, de or pt-BR.

Deleting a template deletes its draft and versions. Emails already sent keep their content, and sends that name it fail from then on.

Permissions

API keys can manage templates (emailTemplate) and send them (email:send). In the dashboard, owners and admins edit, publish and delete templates; members can read them. Test sends from the dashboard go only to members' verified addresses and count against the environment's send quota.