Manage Transactional Emails API
Create and edit transactional email definitions and their bound template from JSON.
Author transactional emails outside the UI — from a script, a CI job, or an AI agent. The definition and its content are one JSON document, so the copy of an email your application sends can live in your repository and be reviewed like code.
Endpoints
| Method | Path | Effect |
|---|---|---|
POST | /api/v1/email/transactional | Create. Requires slug, name, subject, and non-empty contentBlocks. |
GET | /api/v1/email/transactional | List active definitions. |
GET | /api/v1/email/transactional/:slug | Read one, content included — the same document POST accepts. |
PATCH | /api/v1/email/transactional/:slug | Partial update. Any omitted field is left alone. |
POST | /api/v1/email/transactional/send | Send one. |
:slug accepts the definition id as well. The list endpoint only returns active definitions; read an archived one by slug.
The transactional document
A transactional is stored as two rows: the definition (slug, name, status) and the email template that holds the subject and content. The API writes both from a single payload, so you never create the template separately.
{
"slug": "freebie-kit",
"name": "Freebie kit delivery",
"description": "Sent by the app when a lead requests the free kit.",
"subject": "Your kit has arrived",
"previewText": "Print it before Sunday",
"status": "active",
"contentBlocks": [
{ "type": "snippet", "props": { "snippetId": "nh79…" } },
{ "type": "text", "props": { "markdown": "Hi @{firstName|there}," } },
{
"type": "button",
"props": {
"label": "Download the kit",
"url": "@{kitLink|https://example.com/}",
"align": "center",
"backgroundColor": "#2563eb"
}
}
]
}Fields
| Field | Type | Description |
|---|---|---|
slug | string | Required on create. Lowercase letters, digits, - and _. This is what …/transactional/send takes. |
name | string | Required on create. Shown in the Transactional list. |
description | string | Free-form note. Send "" to clear it. |
subject | string | Required on create. Stored on the template; supports @{key|fallback}. |
subjectOverride | string | Replaces the template subject for this definition only. Send "" to clear it. |
previewText | string | Inbox preview line. |
contentBlocks | array | Required and non-empty on create. Same { "type", "props" } blocks as workflow email steps. |
templateName | string | Name of the bound template when listed on its own. Defaults to name. |
status | string | active (default) or archived. An archived definition rejects sends with 409. |
Placeholders work the same as in a workflow email: @{key|fallback} is interpolated everywhere in a block, including a button's url, so a send can pass a per-recipient link through variables. Only a media block's src is exempt.
Create
curl -X POST https://growth.example.com/api/v1/email/transactional \
-H "x-api-key: org_api_key" \
-H "Content-Type: application/json" \
-d @freebie-kit.json{ "transactional": { "id": "k57…", "slug": "freebie-kit", "status": "active" }, "created": true }Edit
PATCH only the fields you want. Sending any of subject, previewText, contentBlocks, or templateName rewrites the bound template in place — existing sends keep their rendered copy.
curl -X PATCH https://growth.example.com/api/v1/email/transactional/freebie-kit \
-H "x-api-key: org_api_key" \
-H "Content-Type: application/json" \
-d '{"status":"archived"}'Error responses
| Status | Meaning |
|---|---|
400 | Invalid JSON body, a malformed slug, or a missing required field. |
401 | Missing or invalid API key. |
404 | Unknown transactional, or one belonging to another organization. |
409 | The slug is already taken, or the bound template was deleted and the payload carries no content to recreate it. |
Notes
- There is no
DELETE. Archive instead: history and stats stay attached to the definition. - Editing content does not resend anything. It changes what the next send renders.
growth-cliwraps this:transactional create --file,transactional update <slug> --file,transactional list. See the growth-cli guide.