Theme

Templates API

Layouts and components — the HTML and CSS an email is dressed in. Create, read, edit, retire and delete them without opening a browser.

All endpoints need a bearer token (see Overview & auth). The same abilities are on the MCP server as list_templates, get_template, save_template, make_default_layout, archive_template and delete_template.

Layouts and components

A layout is the whole document an email is wrapped in: the <html>, the header, the footer, the unsubscribe link. It must contain the {{ content }} slot, which is where the compiled body is injected. Its css is inlined into a <style> tag at compile time. One layout is the instance default — the one an email without a layout of its own gets.

A component is a reusable fragment — a signature, a promo block — inserted into an email's markdown as {% component "key" %}. It carries a key (lowercase letters, numbers, dashes) and no CSS of its own; the layout around it supplies that.

A layout also picks a theme — the colours, font stacks and page shape its CSS is written against. The theme field takes a theme's key. null — the default — means "follow whichever theme is the instance default", so promoting a different theme reaches the layout without touching it. Every layout comes back carrying both theme (the key it chose, or null) and resolved_theme (the key it actually renders with). A component has no theme: it's expanded into the body, and the body is wrapped in a layout, which has one. See the Themes API for the variables and how to edit them.

Endpoints

Method & pathWhat it does
GET /api/v1/templatesEvery active layout and component. Filters: kind (layout or component), archived (any value includes archived ones). Markup is left out of the list — read one to get it.
POST /api/v1/templatesCreate one. Body: name, kind, key (components), html, css, theme (layouts). Omit html and a layout starts from a working default that already has the slot; omit css and it starts from a matching stylesheet — one column at a readable measure, and every element an email uses already styled. That starter is written entirely against theme tags: every colour, font and size in it is a {{ theme.… }} tag rather than a literal, with a @media (prefers-color-scheme: dark) block carrying the dark counterpart of each colour.
GET /api/v1/templates/:idOne template with its html and css — and, for a layout, its theme and resolved_theme. A component may be addressed by its key instead of its id.
PATCH /api/v1/templates/:idUpdate name, key, html, css or theme. kind is ignored — a layout never becomes a component. Send is_default: true to make a layout the default, or archived: true/false to retire or restore it.
DELETE /api/v1/templates/:idDelete it. Refused with 409 for the default layout — make another layout the default first.

Creating a layout

curl -X POST https://your-instance.example.com/api/v1/templates \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Newsletter",
    "kind": "layout",
    "html": "<div class=\"wrap\">{{ content }}</div>\n<p>{{ mailing_address }}<br><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></p>",
    "css": ".wrap { max-width: {{ theme.content_max_width }}; margin: 0 auto; color: {{ theme.body_text_color }}; }",
    "theme": "brand-b"
  }'

\n is how a multi-line document goes into a JSON string — the markup is stored exactly as sent, newlines and all.

The response is the stored template:

{
  "template": {
    "id": 7,
    "name": "Newsletter",
    "kind": "layout",
    "key": null,
    "is_default": false,
    "archived": false,
    "theme": "brand-b",
    "resolved_theme": "brand-b",
    "updated_at": "2026-08-02T12:00:00Z",
    "html": "<div class=\"wrap\">{{ content }}</div>\n…",
    "css": ".wrap { max-width: {{ theme.content_max_width }}; … }"
  }
}

The markup and the stylesheet are stored exactly as written, theme tags and all — the values are substituted when an email is compiled, not when the layout is saved. Send "theme": null instead and the layout follows the default theme, which is what resolved_theme would then name.

Theme tags

A theme tag is a value from a theme written into a layout's css (or its html). Four forms:

{{ theme.body_text_color }}               the layout's theme, light value
{{ theme.dark.body_text_color }}          the layout's theme, dark value
{{ theme.brand-b.body_text_color }}       another theme, named by key
{{ theme.brand-b.dark.body_text_color }}  another theme, dark value

They are resolved at compile time, before Liquid runs — substituted into the stored source, so the renderer only ever sees finished values. A theme tag isn't Liquid; it only borrows the syntax, which is why it works in a stylesheet as readily as in the markup.

A button variable that doesn't name an emphasis means the primary one: {{ theme.button_background_color }} and {{ theme.button_primary_background_color }} are the same value. A tag naming a variable that doesn't exist is refused with 422 when the layout is saved, with the bad tag named — a typo can't quietly compile to a hole.

The CSS is still yours. Replace a theme tag with a literal value and that one declaration simply stops following the theme, while the rest keep going. A layout written with no theme tags at all is a perfectly good layout — it just won't answer to a theme.

Liquid available in a layout

TagWhat it resolves to
{{ content }}The compiled email body. Required — a layout without it is refused. Any inner spacing works: {{content}} and {{ content }} are the same slot.
{{ unsubscribe_url }}A signed, per-recipient link to Mimeo's own unsubscribe page, which unsubscribes on load and then shows a confirmation or sends the person on to a URL of yours (sending.unsubscribe_page_url). Put it in the footer of every layout.
{{ mailing_address }}The account's physical mailing address, from settings (sending.mailing_address). Put it in the footer too — anti-spam law requires marketing email to show one. Resolves at send time, so changing the setting changes every future send.
{{ theme.<variable> }}A value from the layout's theme — {{ theme.link_color }}, {{ theme.content_max_width }}. {{ theme.dark.<variable> }} is the dark half of a colour, and {{ theme.<key>.<variable> }} reads another theme by key. Not really Liquid: these resolve first, before the renderer runs, which is why they work in the css too. Theme tags.

The stylesheet is rendered as Liquid alongside the markup once the theme tags are substituted, so any other Liquid syntax written into the CSS — even inside a comment — is a tag the renderer will try to run, and an unknown one stops the render.

A layout brought over from another tool usually carries that tool's slot name — {{ message_content }} is the common one. Rename it to {{ content }}; the validation error names the slot it found so you don't have to hunt for it.

Making a layout the default

curl -X PATCH https://your-instance.example.com/api/v1/templates/7 \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_default": true}'

There is exactly one default layout: the previous holder steps down in the same request. Archiving the default also stands it down, so another active layout takes over.

Retire or delete

archived: true is the gentler retirement — emails already built on the template keep rendering, and it stops being offered for new work. Restore with archived: false. DELETE removes it outright; emails pointing at a deleted layout fall back to the default.

Saving a template's html or css recompiles every email built on it, so a fix to a layout reaches drafts and queued sends without touching them one at a time. Saving the theme a layout points at does the same thing, which is how one colour change reaches every email at once.

Errors

StatusWhen
422A layout with no {{ content }} slot, a theme tag naming a variable that doesn't exist, a component with a missing, malformed or already-taken key, or a missing name. The errors array names the field — or the bad tag — and the fix.
409Deleting the default layout. Make another layout the default first.
404No template with that id or key.