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).descriptionis optional.- Every field must compile when saved. Partials and decorators are refused. Both fail with
invalid_template, whosedata.fieldnames the field.
Variables and escaping
| Variable | Value |
|---|---|
{{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. |
htmlandpreviewTextescape{{...}}values for HTML.{{{...}}}inserts a value raw.subjectandtextare not escaped.previewTextis 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 indata.field.
Markdown templates
With format: "markdown", html holds Markdown:
- Handlebars renders it first, escaping values for HTML and Markdown, so params add text, never markup.
- Up to 100,000 characters are converted to HTML.
- 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 passesparamsper entry. - Without a stored template, pass
renderwith your ownsubject,htmlandtextto 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
| Route | Does |
|---|---|
GET /v1/templates | Lists 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.