Theme

Themes API

A theme holds the creative decisions an email is built from — colors, font stacks, and the shape of the page. Read them, edit them, and point a layout at one.

All endpoints need a bearer token (see Overview & auth). The same abilities are on the MCP server as list_themes, get_theme, save_theme, make_default_theme and delete_theme.

What a theme is

Every email that goes out is wrapped in a layout, and a layout is HTML and CSS. Without a theme, the decisions inside that CSS — the brand blue, the body font, how wide the content column runs — are literal values typed into a stylesheet. Change the brand blue and you have to find every stylesheet that quoted it, and the one you miss keeps sending the old color.

A theme is where those decisions live instead. The layout's CSS references them as tags, so the value exists in one place and the stylesheets follow it:

.email-content {
  max-width: {{ theme.content_max_width }};
  padding: {{ theme.content_padding_top }} {{ theme.content_padding_sides }} {{ theme.content_padding_bottom }};
  background: {{ theme.content_background_color }};
  border: {{ theme.content_border_thickness }} solid {{ theme.content_border_color }};
  border-radius: {{ theme.content_border_radius }};
}

a { color: {{ theme.link_color }}; }

The tags resolve at compile time, before Liquid runs — what reaches the recipient is ordinary CSS with real values in it. An unknown variable is refused when the layout is saved, and the error names the tag it couldn't resolve.

Saving a theme recompiles every email built on it, so changing a brand color reaches drafts already written and sends already queued, without opening any of them.

Endpoints

Method & pathWhat it does
GET /api/v1/themesEvery theme, plus variables — the whole vocabulary, each with its name, type, group, label, help and default — and button_styles, the three emphases a button tag may name.
GET /api/v1/themes/:keyOne theme, with its variables resolved: every variable comes back with a value whether it was set on this theme or left at its default.
POST /api/v1/themesCreate one. Body: name, optional variables, optional is_default. The key is derived from the name.
PATCH /api/v1/themes/:keyUpdate it. Partial by default — only the variables you send change. Send replace: true to treat variables as the whole set, or is_default: true to make it the default.
DELETE /api/v1/themes/:keyDelete it. Refused with 409 for the default theme, and for a theme any layout is still using.

A theme is addressable by its key or its numeric id, so /api/v1/themes/brand-b and /api/v1/themes/4 reach the same record.

Reading a theme

The list gives you the vocabulary — what a theme can hold — alongside the themes themselves. Read it once and you know every variable name, its type, and what it falls back to:

{
  "themes": [
    { "id": 1, "key": "default",  "name": "Default",  "is_default": true },
    { "id": 4, "key": "brand-b",  "name": "Brand B",  "is_default": false }
  ],
  "variables": [
    {
      "name": "link_color",
      "type": "color",
      "group": "colors",
      "label": "Link color",
      "help": "Links in the body and footer.",
      "default": { "light": "#2563eb", "dark": "#94c6ff" }
    },
    …
  ],
  "button_styles": ["primary", "secondary", "tertiary"]
}

Reading one theme resolves its variables, so you never have to merge a theme against the defaults yourself — a color arrives as a light/dark pair and a size as a string, whether it was set here or inherited:

{
  "theme": {
    "id": 4,
    "key": "brand-b",
    "name": "Brand B",
    "is_default": false,
    "variables": {
      "body_font_stack": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif",
      "link_color": { "light": "#7c3aed", "dark": "#c4b5fd" },
      "content_max_width": "560px"
    }
  }
}

The variables

Three types. A color carries a light value and a dark one. A length is CSS — px, rem, em or %, and a bare number is read as pixels. A font is a plain font-stack string.

Typography

VariableTypeDefault
body_font_stackfont-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif
headline_font_stackfontBlank — blank means headlines use the body font

Colors

VariableLightDarkWhat it paints
email_background_color#f3f4f6#101623Behind everything
content_background_color#ffffff#1f2637The content area itself
heading_color#111827#f9fafbHeadlines and bold text
body_text_color#1f2937#e5e7ebBody copy
muted_text_color#6b7280#9ca3afThe footer and quoted text
link_color#2563eb#94c6ffLinks
hr_color#e5e7eb#374151Dividers
content_border_color#e5e7eb#47556bThe border around the content area

Buttons

VariableTypeLightDark
button_primary_background_colorcolor#2563eb#a3ceff
button_primary_text_colorcolor#ffffff#111827
button_secondary_background_colorcolor#111827#f9fafb
button_secondary_text_colorcolor#ffffff#111827
button_tertiary_background_colorcolor#e5e7eb#374151
button_tertiary_text_colorcolor#111827#f9fafb
button_border_radiuslength6px

Layout

VariableTypeDefault
content_max_widthlength560px
content_border_thicknesslength1px
content_border_radiuslength8px
hr_thicknesslength4px
content_padding_toplength30px
content_padding_sideslength30px
content_padding_bottomlength30px
content_margin_toplength20px
content_margin_sideslength20px
content_margin_bottomlength20px

Padding and margin come in threes — top, left-and-right, bottom — which is the shape of the CSS shorthand they're written into. Left and right differing is a thing almost nobody wants and everybody would have to skip past.

Font stacks

GET /api/v1/themes also returns font_stacks: the stacks the theme editor offers, each with a label, a category (Sans serif, Serif, Monospace) and the stack itself. They are the ones that actually resolve in mail clients — a webfont that looks right in a browser and falls back to Times in Outlook is the usual way an email arrives looking broken. Any other stack can still be set; the list is a shortcut, not a restriction.

Don't hard-code this list. GET /api/v1/themes returns the vocabulary the instance is actually running, defaults and all. Read it at the start of a session and you can't drift out of date with a variable added after this page was written.

Light and dark

Every color is two decisions, not one: mail clients honor the reader's system setting, and a palette picked for a white page is unreadable on a black one. So a color variable carries both values, and there are two tags for it:

{{ theme.body_text_color }}        the light value
{{ theme.dark.body_text_color }}   the dark value

Which one you write is a decision about where in the stylesheet you are. The light values go in the ordinary rules; the dark ones go in a @media (prefers-color-scheme: dark) block:

.email-content {
  background: {{ theme.content_background_color }};
  border-color: {{ theme.content_border_color }};
}

@media (prefers-color-scheme: dark) {
  .email-content {
    background: {{ theme.dark.content_background_color }};
    border-color: {{ theme.dark.content_border_color }};
  }
}

A layout can also reach past its own theme and name another one by key — {{ theme.brand-b.link_color }}, or {{ theme.brand-b.dark.link_color }} for its dark value.

Over the API a color is that pair, written as an object:

{ "variables": { "link_color": { "light": "#7c3aed", "dark": "#c4b5fd" } } }

The three button styles

A theme defines three button emphases — primary, secondary and tertiary — each a background and a text color, sharing one button_border_radius. A button in an email's markdown names the one it wants:

{% button label:"Start your trial" url:"https://example.com/signup" style:"primary" %}

Omit style and you get the primary one. The same shorthand works in a stylesheet: a button tag with no emphasis in its name means the primary, so {{ theme.button_background_color }} and {{ theme.button_primary_background_color }} are the same variable.

The colors come from the theme rather than from the email, so editing a button color recompiles every email that uses one. A button whose light and dark colors differ gets a @media (prefers-color-scheme: dark) rule of its own; a button that looks the same either way gets none, so nothing is emitted that isn't doing work.

Partial and replace updates

PATCH is partial by default. Send the one variable you're changing and nothing else moves — the rest of the theme keeps whatever it had, set or defaulted:

curl -X PATCH https://your-mimeo.com/api/v1/themes/brand-b \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "variables": {
      "link_color": { "light": "#7c3aed", "dark": "#c4b5fd" }
    }
  }'

That's the safe default for an agent: it can't quietly reset a variable it never read. When you do want the payload to be the whole truth — an import, a theme rebuilt from a file — send replace: true and anything absent from variables goes back to its default:

{
  "replace": true,
  "variables": {
    "link_color": { "light": "#7c3aed", "dark": "#c4b5fd" },
    "content_max_width": "640px"
  }
}

Values are checked before anything is stored. A malformed color or an unusable length is refused with 422 and the field named, rather than saved and discovered later in someone's inbox.

The default theme

One theme is the instance default. is_default: true on a POST or PATCH hands it the title, and the previous holder steps down in the same request — there is always exactly one.

curl -X PATCH https://your-mimeo.com/api/v1/themes/brand-b \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_default": true}'

Pointing a layout at a theme

A layout carries a theme field, which takes a theme key:

curl -X PATCH https://your-mimeo.com/api/v1/templates/7 \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"theme": "brand-b"}'

Send "theme": null to clear it. That isn't "no theme" — it means the layout follows whichever theme is the default, so changing the default reaches it. Because the two cases read differently, a layout also reports resolved_theme: theme is the choice, resolved_theme is what it actually renders with.

Components have no theme of their own. A component is expanded into the body, and the body is wrapped in a layout — so it inherits whatever that layout uses.

Errors

StatusWhen
422A missing name, an unknown variable name, or a value that isn't valid for its type. The errors array names the field.
409Deleting the default theme — make another theme the default first — or deleting a theme still in use. The error names the layouts holding it.
404No theme with that key or id.

The in-use check covers both ways a layout can depend on a theme: the one it points at with theme, and any theme its markup names by key in a {{ theme.brand-b.… }} tag. Deleting either would leave a layout that no longer compiles, so the delete is refused instead and you're told which layouts to fix.

See also: Templates API · MCP