Templates
Stored subjects and bodies with {{variable}} placeholders, addressed by slug and rendered with HTML-escaped values.
A template is a stored subject, HTML body and optional plain-text body with {{name}} placeholders. Send with one by passing template and variables on POST /v1/emails, or name it as a step's template_slug in an automation. The copy is written and reviewed once, and the values filled in at send time are escaped, so they cannot rewrite the message around them. The Templates guide shows the whole flow.
Templates are addressed by slug, not by id.
Variables
A placeholder is two braces around a name: {{name}}. Spaces inside the braces are allowed, so {{ name }} is the same placeholder. A name is letters, digits, underscores and dots. A dot is part of the name, not a path: {{user.name}} takes the value whose key is "user.name". Anything else between braces, such as {{first-name}}, is not a placeholder and is sent literally.
The placeholders in the subject, the HTML body and the text body are collected into the template's variables list whenever it is saved. Every name on that list must have a value when the template is rendered, even one that appears only in the text body. An empty string counts as a value; a missing key or null does not. Values for names the template does not use are ignored.
Values are HTML-escaped when substituted into the HTML body: &, <, >, " and ' become entities. Values come from agents and end users, and a name like <script> would otherwise run in whatever webmail displays it, or a prompt-injected value could rewrite the surrounding message. There is no syntax for inserting raw HTML. The subject and the text body are not HTML, so values go into them as given.
The template object
Every field is always present.
| Field | Type | Description |
|---|---|---|
id | string | The template's id (a UUID). Not used by any endpoint. |
slug | string | Lower-case letters, digits and hyphens. Unique within the workspace. |
name | string | Display name. |
subject | string | The subject line, with placeholders. |
html | string | The HTML body, with placeholders. |
text | string | null | The plain-text body. null when the template has none. A template saved before empty strings were refused may hold "", which counts as no text body when rendering. |
variables | array of string | Every placeholder name in the subject and both bodies, sorted and without duplicates. All are required to render. |
created_at | string (ISO 8601) | When the template was created. |
updated_at | string (ISO 8601) | null | When the template was last updated. null until the first update. |
List templates
GET /v1/templates · requires templates:read
Returns every template in the workspace, sorted by name, in the list envelope without paging. Up to 500 are returned; at 500, has_more is true. Each entry carries variables, so this tells you what a template needs before you render or send it.
Example
Response
200 OK
Create or update a template
POST /v1/templates · requires templates:write
Creates a template, or updates the one with this slug if it already exists. A new template returns 201; an existing one returns 200 with the stored template after the update. Either way variables is recomputed from the new copy.
On an update, name, subject and html are replaced. text is replaced when given as a string, kept when left out, and removed when null; variables is recomputed either way, so a placeholder used only in the removed text body leaves the list. An empty string is refused with 422 invalid_request, because it is neither: send null to remove the text body.
Changing a template changes every later send that uses it, including automation steps already scheduled for people partway through a sequence. If the new copy adds a placeholder, those steps fail unless the enrolments carry a value for it.
Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
slug | string | yes | Lower-case letters, digits and hyphens. | |
name | string | yes | 1 to 200 characters. | |
subject | string | yes | 1 to 998 characters. May contain placeholders. | |
html | string | yes | At least 1 character. May contain placeholders. | |
text | string | null | no | none | Plain-text body. May contain placeholders. On an update, left out keeps the stored text body and null removes it. At least one character; "" is refused. |
Example
Response
201 Created for a new template, with updated_at null.
200 OK for an update, with updated_at set.
Errors
| Status | Type | When |
|---|---|---|
| 422 | invalid_request | text is an empty string. The message says to send null to remove the text body. |
| 422 | invalid_request | The body failed validation. details lists each field. |
Retrieve a template
GET /v1/templates/{slug} · requires templates:read
Returns one template.
Path parameters
| Parameter | Description |
|---|---|
slug | The template's slug. |
Example
Response
200 OK
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | No template with this slug in the workspace. |
Delete a template
DELETE /v1/templates/{slug} · requires templates:write
Deletes the template. A template that an active or paused automation renders a step from cannot be deleted: the call answers 409 invalid_state naming each such automation, because its step would fail every enrolment that reached it. Delete those automations, or set them to draft, first. Nothing else is checked: a later send naming a deleted template is refused, and a draft automation naming it fails its enrolments once it is activated. Campaigns carry their own HTML and never depend on a template.
Path parameters
| Parameter | Description |
|---|---|
slug | The template's slug. |
Example
Response
200 OK
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | No template with this slug in the workspace. |
| 409 | invalid_state | An active or paused automation has a step using this template. The message names each one (up to 20) with its id and status. Nothing is deleted. |
Render a template
POST /v1/templates/{slug}/render · requires templates:read
Renders the template with the values given and returns the result without sending anything. Use it to check that the values produce sensible copy before a real person sees it. It follows the rules in Variables exactly as a send does: values are escaped the same way, and a missing value is refused the same way.
Path parameters
| Parameter | Description |
|---|---|
slug | The template's slug. |
Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
variables | object | no | {} | Placeholder name to value. A string is used as given; a number or boolean is turned into its string form (3, true). An object or array is refused with 422, and null counts as no value. POST /v1/emails is stricter and accepts only strings. |
Example
Response
200 OK
text is always present: null when the template has no text body, or an empty one.
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | No template with this slug in the workspace. |
| 422 | invalid_request | variables is not an object, or one of its values is an object or an array. The message names the value, for example variables.plan. |
| 422 | missing_variables | One or more of the template's variables has no value. The error object adds missing, an array of the names, for example { "error": { "type": "missing_variables", "message": "Template is missing values for: plan", "missing": ["plan"] } }. |
Duplicate a template
POST /v1/templates/{slug}/duplicate · requires templates:write
Copies the template's subject, HTML and text bodies under a new slug. The copy is a new template with its own id and created_at; later changes to either do not affect the other.
Path parameters
| Parameter | Description |
|---|---|
slug | The slug of the template to copy. |
Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
slug | string | yes | The new template's slug. Lower-case letters, digits and hyphens, and unused in the workspace. | |
name | string | no | the source's name followed by copy | Surrounding whitespace is trimmed, then 1 to 200 characters. An empty or blank name is refused. |
Example
Response
201 Created
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | No template with the source slug in the workspace. Checked before the body. |
| 409 | conflict | A template with the new slug already exists. |
| 422 | invalid_request | slug is missing or not lower-case letters, digits and hyphens, or name is empty, blank or longer than 200 characters. The message names each field that failed, and details lists them. |