A template is an email the platform keeps for you: a subject, sender fields and content with {{variables}}, in numbered versions. A send names the template and passes the variables; the platform renders the published version, escapes every value for where it lands, and sends. The content is a block document or uploaded HTML.

Create a template

curl
curl https://api.minket.com/v1/templates \
  -H "Authorization: Bearer $MINKET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Password reset",
    "alias": "password-reset",
    "subject": "Reset your password, {{name}}",
    "from": "Acme <account@mail.acme.example>",
    "document": {
      "version": 1,
      "blocks": [
        { "type": "heading", "level": "1", "text": "Reset your password" },
        { "type": "paragraph", "text": "Hi {{name}}, the link below works for **{{minutes}} minutes**." },
        { "type": "button", "label": "Choose a new password", "href": "{{reset_url}}" }
      ]
    },
    "variables": [
      { "key": "name", "type": "string", "fallback_value": "there", "required": false },
      { "key": "minutes", "type": "number", "fallback_value": 30, "required": false },
      { "key": "reset_url", "type": "string", "fallback_value": null, "required": true }
    ],
    "publish": true
  }'

html takes uploaded markup instead of document (one or the other), with an optional text part; without one, the text part is derived from the HTML. alias is lowercase letters, digits, - and _, unique in the project; sends and the API take it wherever they take the id.

Variables

Form Use
{{name}}, {{contact.first_name}} Lowercase keys, dots for nested values.
{{{NAME}}} Triple braces, any case, escaped the same way: they never mean raw HTML.
{{{NAME|there}}} An inline fallback, used when the send gives no value.

Each variable has a type (string, number or boolean), a fallback_value of that type or null, and required. A send that leaves out a required variable without a fallback, or gives a value of the wrong type, is refused with 422 validation_error naming it; keys the template does not declare are ignored. Every placeholder the content uses is declared when you save, as an optional string, so you only list the ones that need a type, a fallback or required.

Values are escaped for where they land: entity-escaped in HTML text and attributes, kept as they are in the text part, never a line break in the subject. In a URL, a placeholder at the very start is the URL ({{reset_url}}, or {{base_url}}/reset) and the result must be an https: or mailto: link (images: https:), checked once the value is in; a placeholder later in the URL is percent-encoded (https://acme.example/r?token={{token}}). A variable can never add markup, a link or a script.

Broadcasts and automations render for a contact, whose fields fill the variables: {{email}}, {{first_name}}, {{last_name}}, any property key ({{plan}}), the same under {{contact.…}}, and {{unsubscribe_url}} and {{preferences_url}}. Values the send passes win over the contact's, and a contact value of another type than the template declares leaves the fallback.

Versions and publishing

Every update (PATCH /v1/templates/{id}) saves a new version and makes it current_version; POST /v1/templates/{id}/publish pins the current version as published_version. Sends use the published version, so you can edit and preview a draft while sends keep the published one; a send may pin its own with template.version. GET /v1/templates/{id}/versions lists the kept versions, newest first: the newest 50 and the published one.

POST /v1/templates/{id}/duplicate copies the current version into a new draft template without an alias. Deleting a template deletes its versions; sends that name it fail with 404 not_found, and emails already sent are unaffected.

Send with a template

JSON
{
  "to": "jane@example.com",
  "template": { "id": "password-reset", "variables": { "name": "Jane", "reset_url": "https://acme.example/reset/3f9c" } }
}

The template supplies subject, from, reply_to, html and text; any of those in the request wins. A template that was never published is refused with 409 invalid_state unless the send names a version.

Previews

POST /v1/templates/{id}/preview renders a version (the current draft unless you pass version) with the variables you pass and returns subject, html, text and missing. A missing value shows as [key] and is listed in missing rather than refused, so you can preview before you have every value. The console shows the same preview in a sandboxed frame (no scripts, forms or navigation) with its own Content-Security-Policy.

Block documents

Templates and broadcasts store content as a block document, which the platform renders to email-safe HTML (table layout, inline styles, the document's width as a cap and fluid below it, alt text on images, a dark-mode stylesheet for the clients that support one) and a plain-text part. Every value is escaped at render time; only an html block takes raw HTML, sanitized as uploaded HTML is.

JSON
{
  "version": 1,
  "settings": { "width": 600, "background": "#f4f4f5", "font": "system", "footer": "Acme, 1 Example Street, Amsterdam" },
  "blocks": [
    { "type": "heading", "level": "1", "text": "October update" },
    { "type": "paragraph", "text": "Hi {{first_name}}, here is what changed this month: **three** new features and one fix. [Release notes](https://acme.example/changelog)" },
    { "type": "image", "src": "https://acme.example/images/october.png", "alt": "The new dashboard", "width": 560 },
    { "type": "button", "label": "Open the dashboard", "href": "https://acme.example/app" },
    { "type": "divider" }
  ]
}
Block Fields
heading level (1, 2, 3), text, style
paragraph text with inline markup: **bold**, _italic_, [label](https://url); style
image src, alt, optional href, width (16 to 1200), style
button label, href, style
divider, spacer style; spacer has height (4 to 200)
html html, sanitized at render
columns 2 or 3 columns of up to 20 blocks each

style takes color and background as hex, align and padding. settings.width is 320 to 800 (default 600), font is system, serif or mono, and on marketing mail the unsubscribe and preferences links follow footer.

Uploaded HTML

Uploaded markup is usually React Email, compiled on your machine by the CLI or the SDK; the platform never runs your code. It is sanitized when you save it and again when it renders. The sanitizer keeps what email clients render: the document, <head>, <style> (rules and media queries), tables, text, links, images, inline styles, class and data- attributes, and Outlook's conditional comments with their VML. It removes scripts, event handlers (onclick and the rest), javascript:, vbscript: and non-image data: URLs, iframes, forms and their controls, <object>, <embed>, SVG and MathML, <base>, <link>, <meta http-equiv="refresh">, CSS expression(), behavior, -moz-binding and @import, and unsafe url()s in CSS. Links may be https:, http:, mailto:, tel: or relative; images https:, http:, cid: (inline attachments) or raster data: URLs.

Shell
minket templates push ./emails/receipt.tsx   # compiles React Email locally, uploads the HTML

Keys

Templates belong to the project, shared by its environments and by live and test keys. Reading and managing them needs a full_access key; a sending_access key can send with a template but not read or change one. Test keys read, preview and send with templates in test mode, and never change them, since live mail uses the same templates (API keys). The sizes, counts and kept versions are in Limits and retention.

Updated