Templates

Store Handlebars or Markdown content, preview it, and use it in sends, drafts and mail merges.

A template is stored, reusable content for sends, drafts and mail merges. Write it once in Handlebars or Markdown, preview it, then name it in a request.

Create a template

A template is a named subject, html, text and previewText, each written in Handlebars.

curl -X POST "https://api.workwae.com/v1/templates" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "name": "welcome",
  "subject": "Welcome, {{params.firstName}}",
  "previewText": "Your {{params.plan}} plan is ready",
  "html": "<p>Hi {{params.firstName}},</p><p>Welcome aboard. {{account.name}}</p>",
  "text": "Hi {{params.firstName}},\n\nWelcome aboard. {{account.name}}"
}'
  • A template belongs to the tenant and every account can use it. With accountId, it is bound to that account.
  • name (up to 255 characters) is unique per scope (template_name_taken). description is optional.
  • Every field must compile when saved. Partials and decorators are refused. Both fail with invalid_template, whose data.field names the field.

Variables and escaping

VariableValue
{{params.*}}The params of the send, merge entry or render, up to 256 KB as JSON.
{{account.email}}, {{account.name}}The sending account's address and name.
{{rcpt.unsubscribeUrl}}, {{rcpt.address}}, {{rcpt.name}}The recipient's one-click unsubscribe link, address and name, on sends naming a listId (empty otherwise). See Unsubscribe links.
  • html and previewText escape {{...}} values for HTML. {{{...}}} inserts a value raw.
  • subject and text are not escaped.
  • previewText is injected at the top of the HTML body as a hidden block: the line inboxes show next to the subject.
  • Rendering is bounded: at most 10,000 block iterations per render, and each field has a size limit. A render that exceeds one fails with template_render_failed, with the field in data.field.

Markdown templates

With format: "markdown", html holds Markdown:

  1. Handlebars renders it first, escaping values for HTML and Markdown, so params add text, never markup.
  2. Up to 100,000 characters are converted to HTML.
  3. Without a text, the plain-text part is the rendered Markdown.
await workwae.templates.create({
  name: "weekly-digest",
  format: "markdown",
  subject: "Your week, {{params.firstName}}",
  html: `# Hi {{params.firstName}}

{{#each params.items}}
- **{{this.title}}**: {{this.summary}}
{{/each}}

[Unsubscribe]({{rcpt.unsubscribeUrl}})`,
});

Preview without sending

POST /v1/template-renders renders a stored template (templateId) or inline fields with params, exactly as a send would, and stores nothing. Pass accountId to fill in {{account.*}}.

curl -X POST "https://api.workwae.com/v1/template-renders" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "templateId": "'"$TEMPLATE_ID"'",
  "accountId": "'"$ACCOUNT_ID"'",
  "params": { "firstName": "Ada", "plan": "Pro" }
}'

Send with a template

  • Sends, drafts and mail merges name a template with template (its id).
  • Sends and drafts pass values with render.params; a mail merge passes params per entry.
  • Without a stored template, pass render with your own subject, html and text to render inline content.
  • A template's fields replace the inline ones, which cannot be given with it.
  • Content is rendered when the request is taken: editing or deleting the template later never changes queued mail.
curl -X POST "https://api.workwae.com/v1/accounts/$ACCOUNT_ID/outbox" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "to": [{ "name": "Ada", "address": "ada@example.com" }],
  "template": "'"$TEMPLATE_ID"'",
  "render": { "params": { "firstName": "Ada", "plan": "Pro" } }
}'

List, update and delete

RouteDoes
GET /v1/templatesLists templates newest first, without content. Filters: usableBy (shared ones plus the account's own), accountId (bound to an account), name (exact), query (name substring).
GET /v1/templates/{templateId}Reads one with its content.
PATCH /v1/templates/{templateId}Changes the fields given.
DELETE /v1/templates/{templateId}Removes it. Queued sends keep what they were rendered with.
import { paginate } from "@workwae/sdk";

for await (const summary of paginate(workwae.templates.list, { usableBy: accountId })) {
  console.log(summary.name, summary.source, summary.updatedAt);
}

Templates from react-email

A template with source: "react-email" was rendered from a react-email component in code and uploaded under its name. The console marks it as generated: edits made there are overwritten when it is uploaded again.

All routes are in the Templates reference.

On this page