API/Manage Transactional Emails API

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

MethodPathEffect
POST/api/v1/email/transactionalCreate. Requires slug, name, subject, and non-empty contentBlocks.
GET/api/v1/email/transactionalList active definitions.
GET/api/v1/email/transactional/:slugRead one, content included — the same document POST accepts.
PATCH/api/v1/email/transactional/:slugPartial update. Any omitted field is left alone.
POST/api/v1/email/transactional/sendSend 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

FieldTypeDescription
slugstringRequired on create. Lowercase letters, digits, - and _. This is what …/transactional/send takes.
namestringRequired on create. Shown in the Transactional list.
descriptionstringFree-form note. Send "" to clear it.
subjectstringRequired on create. Stored on the template; supports @{key|fallback}.
subjectOverridestringReplaces the template subject for this definition only. Send "" to clear it.
previewTextstringInbox preview line.
contentBlocksarrayRequired and non-empty on create. Same { "type", "props" } blocks as workflow email steps.
templateNamestringName of the bound template when listed on its own. Defaults to name.
statusstringactive (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

StatusMeaning
400Invalid JSON body, a malformed slug, or a missing required field.
401Missing or invalid API key.
404Unknown transactional, or one belonging to another organization.
409The 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-cli wraps this: transactional create --file, transactional update <slug> --file, transactional list. See the growth-cli guide.