Templates
Stored copy with `{{variables}}`, rendered safely at send time.
A template is a stored subject and body with {{name}} placeholders. Prefer sending through one over composing HTML in the caller: the copy is reviewed once, and the values filled in at send time cannot rewrite the message around them.
Creating one
POST /v1/templates takes a slug (lower-case letters, digits and hyphens), a name, a subject, html and an optional text body. Posting a slug that already exists updates it in place and returns 200; a new one returns 201. Placeholders are extracted from the subject and both bodies and returned as variables, so GET /v1/templates tells you what each template needs before you render it.
POST /v1/templates/{slug}/duplicate copies one under a new slug — 409 slug_taken if the new slug is in use — and DELETE /v1/templates/{slug} removes one.
curl -X POST https://api.example.com/v1/templates \
-H "Authorization: Bearer $API_KEY" \
-d '{
"slug": "welcome",
"name": "Welcome",
"subject": "Welcome to {{plan}}, {{name}}",
"html": "<p>Hi {{name}}, your {{plan}} workspace is ready.</p>"
}'Rendering without sending
POST /v1/templates/{slug}/render with { "variables": { … } } returns the rendered subject, html and text without mailing anyone. It exists so an agent can check that its values produce sensible copy before a real person sees it.
curl -X POST https://api.example.com/v1/templates/welcome/render \
-H "Authorization: Bearer $API_KEY" \
-d '{"variables": {"name": "Ana", "plan": "Pro"}}'Sending with one
Pass template and variables on POST /v1/emails instead of subject and html. subject becomes optional and overrides the template's own when given. POST /v1/emails/batch accepts the same fields per entry and resolves them identically — a batch that quietly ignored templates would be a trap.
{
"from": "Team <team@mail.example.com>",
"to": "ana@example.com",
"template": "welcome",
"variables": { "name": "Ana", "plan": "Pro" }
}Values are escaped
A missing variable is an error
Rendering fails rather than mailing a message with {{name}} still visible in it. A half-rendered email reaching a customer is worse than a failed call the caller can fix. From the render endpoint the error is 422 missing_variables; from POST /v1/emails it is 422 invalid_request. Both carry a missing array naming exactly which values were absent.
An empty string counts as provided. Only a value that is missing altogether fails the render.
Next: Campaigns