Skip to content

Templates ​

Reusable message content: a subject, an HTML and/or plain-text body, and {{ placeholder }} variables filled in at send time.

The template object ​

json
{
  "id": "…",
  "name": "Welcome",
  "subject": "Welcome, {{ contact.name }}",
  "body_html": "<p>Hello {{ contact.name }}</p>",
  "body_text": "Hello {{ contact.name }}",
  "status": "published",
  "editor_state": null,
  "folder_id": null,
  "created_at": "…",
  "updated_at": "…"
}

editor_state is the dashboard editor's own block document. Leave it alone from the API — a template created without one opens in the editor as a single raw-HTML block, which is fine.

Draft and published ​

status is draft or published, and it is enforced rather than decorative: a draft cannot be used to send.

PathWhat a draft does
POST /v1/send with template_id422 notifyzr.template.error.not_published, before the message is recorded and before quota is spent
A broadcastRefused at creation
An automation send_email actionThe step is recorded as skipped — an automation has no caller to hand a 422 to

Three rules about the status itself:

  • A created template defaults to draft. Pass "status": "published" to publish on creation.
  • An update that omits status keeps the stored one. The editor saves constantly and must never unpublish your live content by accident.
  • A draft may be empty; publishing needs at least one body. Publishing without one is 422 notifyzr.template.error.body_required.

Create, update, delete ​

POST   /v1/templates
GET    /v1/templates
GET    /v1/templates/{id}
PATCH  /v1/templates/{id}
DELETE /v1/templates/{id}
FieldRequiredNotes
nameyesUp to 255 characters.
subjectnoUp to 255.
body_htmlno
body_textnoSend both for a multipart email.
statusnodraft (default on create) or published.

GET /v1/templates takes an optional status filter and is not paginated.

Deleting a template does not affect messages already sent.

Placeholders ​

A template's body_html and body_text support {{ placeholder }} interpolation, filled at send time.

json
{
  "message": {
    "subject": "Welcome aboard",
    "template_id": "…",
    "variables": { "contact.name": "Ada" }
  }
}

Keys are flat. "contact.name" fills {{ contact.name }}; a nested {"contact": {"name": …}} does not — the renderer looks the whole placeholder up as a single key rather than walking a path.

Only the template's bodies are rendered. A subject or an inline body[] you send with the request is used verbatim.

In an automation it works the other way round: there is no variables to supply, and placeholders resolve against the triggering event — {{ data.values.email }} reaches a form submission's email field.

An unresolved placeholder renders as an empty string rather than failing the send, so a wrong variable shape shows up as a gap in the email.

Folders ​

POST   /v1/template-folders
GET    /v1/template-folders
PATCH  /v1/template-folders/{id}
DELETE /v1/template-folders/{id}
POST   /v1/templates/{id}/move

Folders are organisational only — they do not affect how a template is resolved. They nest three levels deep; deeper is notifyzr.template_folder.error.depth_exceeded, and moving a folder inside itself is …error.cycle.

json
{
  "id": "…",
  "parent_id": null,
  "name": "Onboarding",
  "template_count": 4,
  "descendant_folder_count": 1,
  "descendant_template_count": 7,
  "created_at": "…",
  "updated_at": "…"
}

template_count is what sits directly in the folder; the two descendant_ counts include everything below it — which is what a delete confirmation needs to say what it is about to remove.

POST /v1/templates/{id}/move takes the destination folder_id, or null for the root.

Released under the Apache 2.0 License.