Templates & Layouts

Search Documentation

Search across all developer documentation

Templates & Layouts

A template has a kind:

  • email — a complete email that a broadcast (template_id) or a workflow send uses. It is deployed to your own SES account.
  • layout — a branded wrapper with exactly one {{> content}} slot. A composed broadcast places its body into that slot. A layout is never deployed to SES and never sent on its own.

Both kinds live under /v1/templates and are addressed by slug.

EndpointScope
GET /v1/templates — ?kind=email or ?kind=layout narrows to one kind; omitted lists bothapi.templates.view
GET /v1/templates/{slug}api.templates.view
PUT /v1/templates/{slug} — create or replaceapi.templates.manage
POST /v1/templates/renderapi.templates.view
POST /v1/templates/{slug}/testapi.templates.test
DELETE /v1/templates/{slug}api.templates.manage

PUT is a full replacement, idempotent, and hash-guarded (expected_content_hash); for an email template it deploys to SES before it answers and reports the outcome in deploy_state. The endpoint reference has the details. An invalid ?kind= on the list is a 422 on field kind.

Kind

kind is email or layout. It defaults to email on create, and an update that omits it keeps the stored kind. It is fixed at create: a PUT naming a different kind for an existing slug is 422 kind_immutable — write a new slug instead.

Layouts

Write a layout with PUT /v1/templates/{slug} and kind: layout:

{
"kind": "layout",
"format": "handlebars",
"body": "<!doctype html><html><body><img src=\"https://cdn.acme.example/logo.png\" alt=\"Acme\" width=\"120\"><div>{{> content}}</div></body></html>"
}

A layout must contain {{> content}} exactly once, inside <body>, and no other partial. A layout write that breaks the rule is a 422 validation_failed with one of these validation_codes:

validation_codeCause
layout_missing_slotNo {{> content}} slot.
layout_multiple_slotsMore than one slot.
layout_slot_outside_bodyThe slot is outside <body>…</body>.
layout_unknown_partialAny other partial, e.g. {{> footer}}.
subject_not_allowed_for_layoutA subject was sent. A layout wraps a broadcast’s body; the broadcast carries the subject.

The reverse holds too: a {{> content}} slot in an email template is refused with content_slot_in_email. A layout also passes the usual SES checks, including the 500 KB limit.

A layout:

  • is never deployed to SES — its deploy_state is not_applicable;
  • does not count toward your plan’s template limit;
  • gets the unsubscribe link and postal address added when a broadcast is composed into it, if your organization has set them in its Email compliance settings — don’t add your own unsubscribe link when the base URL is set.

Render, test and delete

  • Render — POST /v1/templates/render on a layout (stored, or a body containing the slot) fills the slot with a fixed sample body — a heading, a paragraph and a button — so you see where a broadcast’s message lands.
  • Test — POST /v1/templates/{slug}/test on a layout is 422 layout_not_sendable. Test a broadcast that uses it instead, with POST /v1/broadcasts/{id}/test.
  • Delete — refused for the organization’s default layout (422, validation_code: default_layout_not_deletable; make another layout, or the built-in, the default first), and for a layout an unsent broadcast still uses (409 conflict, with the broadcasts in referenced_by). A sent broadcast does not block deleting its layout.

The default layout

Every organization has a default layout: a broadcast created with body and no layout (or layout: "default") uses it, and the choice is frozen into the broadcast at create. Until you choose another, it is the SendOps built-in (layout: "builtin" on a broadcast).

The default layout is not set over /v1

There is no /v1 endpoint to read or change the default layout. Set it in the dashboard (Templates → Layouts → Set as default) or with the MCP tool templates_author and action: make_default. See MCP Server.