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 & path | What it does |
|---|---|
GET /api/v1/templates | Every 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/templates | Create 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/:id | One 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/:id | Update 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/:id | Delete 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
| Tag | What 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
| Status | When |
|---|---|
422 | A 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. |
409 | Deleting the default layout. Make another layout the default first. |
404 | No template with that id or key. |