Mailfully logo

Templates

Store reusable HTML and text bodies with {{variable}} placeholders and render them when a send is accepted.

A template stores html and text bodies with {{variable}} placeholders. A send references the template by id, supplies variables, and the API renders the bodies at accept time.

Templates have no subject field. Each send supplies its own subject in the send request.

You can also manage templates on the dashboard's Templates page.

Create a template

curl -X POST https://api.mailfully.com/v1/templates \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order-shipped",
    "html": "<h1>Your order shipped</h1><p>Hi {{name}}, order {{order_id}} is on its way.</p>",
    "text": "Hi {{name}}, your order {{order_id}} has shipped.",
    "variables_schema": { "required": ["name", "order_id"] }
  }'

name is required, and at least one of html or text must be present. variables_schema (variablesSchema in the SDK) is optional; see Required variables. The 201 response:

{
  "object": "template",
  "id": "tmpl_01J8ZXA3B4C5D6E7F8G9H0J1K2",
  "name": "order-shipped",
  "html": "<h1>Your order shipped</h1><p>Hi {{name}}, order {{order_id}} is on its way.</p>",
  "text": "Hi {{name}}, your order {{order_id}} has shipped.",
  "variables_schema": { "required": ["name", "order_id"] },
  "created_at": "2026-07-08T17:00:00.000Z",
  "updated_at": "2026-07-08T17:00:00.000Z"
}

Templates are org-scoped, not environment-scoped: test-mode and live-mode API keys of the same organization see and mutate the same templates.

Variable syntax

  • Placeholders are {{name}}; whitespace inside the braces is allowed, so {{ name }} also matches.
  • Keys may contain letters, digits, underscores, and dots: [A-Za-z0-9_.].
  • Keys are flat. {{user.name}} looks up the literal key "user.name" in the variables object. It never traverses into a nested object. Pass { "user.name": "Jo" }, not { "user": { "name": "Jo" } }.
  • Values substituted into the html body are HTML-escaped (& < > " ' become entities). Values in the text body are inserted raw.
  • Unknown, null, or missing variables render as an empty string, so the literal placeholder never leaks into a sent email.
  • Numbers and booleans are stringified; objects and arrays are JSON-encoded.
  • Substituted values are not re-scanned, so a {{...}} sequence inside a variable's value is emitted literally.

Send with a template

Reference the template in the send request's template field, with id and optional variables. A template counts as content, satisfying the one-of-html/text/template rule. The examples send to you@yourcompany.com. Swap in an address you own.

curl -X POST https://api.mailfully.com/v1/emails \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "orders@mail.acme.com",
    "to": "you@yourcompany.com",
    "subject": "Your order shipped",
    "template": {
      "id": "tmpl_01J8ZXA3B4C5D6E7F8G9H0J1K2",
      "variables": { "name": "Ada", "order_id": "ord-1042" }
    },
    "tags": [{ "name": "event", "value": "order-shipped" }]
  }'

The response is the standard 202 acceptance. It echoes the addressing and subject, never the rendered body:

{
  "object": "email",
  "id": "01JZFYK2N8Q4T7W9E1R3M5X0AB",
  "status": "queued",
  "type": "transactional",
  "to": ["you@yourcompany.com"],
  "cc": [],
  "bcc": [],
  "from": "orders@mail.acme.com",
  "subject": "Your order shipped",
  "scheduled_at": null,
  "created_at": "2026-09-11T14:15:00.000Z"
}

If the send also carries inline html or text, the template's rendered output replaces both unconditionally.

A template.id that doesn't exist for your organization fails the send with 404 not_found and the message Template not found.

Required variables

variables_schema is stored as-is and never validated at create or update time. At send time, exactly one part of it is enforced: the required string array. Each listed key must be present and non-null in template.variables, or the send is rejected:

{
  "error": {
    "type": "validation_error",
    "message": "Missing required template variable: order_id",
    "param": "variables.order_id"
  }
}

If variables_schema is null or has no required array, no variable validation happens at all: unknown placeholders render as empty strings.

When rendering happens

Templates render when the API accepts the send (at the 202, not at dispatch). The rendered html and text are stored with the message and sent as is.

Consequences:

  • Editing a template never changes messages that are already queued, including sends scheduled days in the future with scheduled_at. They carry the content rendered at accept.
  • Deleting a template never affects accepted sends either. Only new sends referencing the deleted id fail, with 404 not_found.

Update and delete

PATCH /v1/templates/{id} is a partial update: omitted fields are untouched, and an explicit null clears html, text, or variables_schema. Every successful PATCH refreshes updated_at.

curl -X PATCH https://api.mailfully.com/v1/templates/tmpl_01J8ZXA3B4C5D6E7F8G9H0J1K2 \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "text": "Hi {{name}} - order {{order_id}} shipped." }'

PATCH applies no cross-field check. Setting both html and text to null is legal and leaves a template that renders empty bodies on every future send. Create requires at least one body; update does not re-verify it.

DELETE /v1/templates/{id} is an immediate, hard delete:

curl -X DELETE https://api.mailfully.com/v1/templates/tmpl_01J8ZXA3B4C5D6E7F8G9H0J1K2 \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx"
{
  "id": "tmpl_01J8ZXA3B4C5D6E7F8G9H0J1K2",
  "deleted": true
}

The id is not reserved after deletion, and future sends referencing it return 404 not_found.