Definition files — Mimeo docs

Definition files

Your whole email operation as files you own — every format, and the two rules that matter more than the formats do.

Your Mimeo's database is the runtime source of truth. The manager repo holds the things you write, in the format most natural to edit them in — its definition files, specified here. mimeo pull refreshes the files from your Mimeo; mimeo push diffs, validates, rehearses and applies them back.

The layout

flows/<key>.json                    validated flow definitions
segments/<key>.json                 rules definitions
guards/<id>-<name>.json             condition + action + scope
sequences/<id>-<name>.yml           ordered email refs + delays + step status + repeat mode
emails/<id>-<subject>.md            markdown body + YAML frontmatter
templates/layouts/<id>-<name>.html|.css
templates/components/<key>.html|.css
templates/layouts.yml               each layout's theme + which layout is the default
media/<filename>                    the media library's files themselves
media.yml                           each media file's hosted URL + alt text + id
fields.yml                          custom field definitions
products.yml                        product keys + names
themes.yml                          colours, type and page shape layouts are built from
settings.yml                        tunables, send windows, mailing address

Flows, segments, guards, fields, products and template components have stable keys. Emails and sequences don't, so their filenames carry their numeric id — which is also what a flow definition references. The id is in the file too, in the JSON or the frontmatter; the filename is a convenience, and renaming an email simply renames its file on the next pull.

What never lives here

People, events, flow and sequence runs, scheduled and sent emails, opens, clicks, purchases, subscriptions and payments. All runtime, all your Mimeo's. Also broadcasts: a broadcast carries a send time and a resolved audience, which makes it runtime even though the email it sends is authored.

And no credentials. settings.yml carries tunables, send_windows and sending.mailing_address, and nothing else. Provider keys, storage keys and webhook secrets are entered in the admin UI and stay encrypted on your Mimeo — push refuses a settings.yml that carries one, by name, rather than dropping it quietly.

The two rules that matter

Node ids are never rewritten

A step's id is what the runs of people standing on it point at. pull writes ids exactly as it finds them and never renumbers; push sends the file through the normalizer with your Mimeo's current definition as the baseline, so a stale node_counter in a merged file can't drag the counter backwards and free ids for reuse.

Give a new step an id an old step had and everyone parked there silently lands somewhere they were never sent. push refuses over it — see the CLI page. Leave the id off a genuinely new step and Mimeo mints one; run pull afterwards and the file catches up.

Sharing is expressed by the reference, not the content

An email attached to a step is either the shared library row — edit it, and every use of it changes — or an independent copy. In the repo that distinction is simply how many things point at one file: two steps naming the same emails/x.md are the shared choice; a copy is its own file.

Two things fall out of that, and both are load-bearing:

Because every broadcast mints its own email row, emails/ accumulates files that exist for exactly one send and were never meant for reuse. That's expected, not drift. Where the admin has the used-in filter for telling reusable content apart, the repo has the sequence and flow manifests: an email no manifest names is either a broadcast's or unused.

The formats

flows/<key>.json

{
  "key": "trial_journey",
  "name": "Trial journey",
  "labels": ["onboarding", "revenue"],
  "run_policy": "single_run",
  "definition": {
    "trigger": { "event": "signed_up", "match": null },
    "node_counter": 11,
    "nodes": [ ... ]
  }
}

definition is byte-identical to what the API takes and the database stores — see the flow schema. status is deliberately absent: activating a flow is an action, not a field to sync.

labels is the flow's whole grouping — what's in the file is what the flow carries, so removing one here removes it on push. Reuse a label the other files already use rather than coining a synonym of it.

segments/<key>.json

{ "key": "quiet_trials", "name": "Trials who went quiet",
  "description": "…", "should_be_empty": false,
  "rules": { "type": "all", "rules": [ … ] } }

guards/<id>-<name>.json

{ "id": 3, "name": "Members skip pitches", "action": "skip",
  "scope_kind": "label", "scope_value": "pitch", "enabled": true,
  "condition": { "type": "has_tag", "tag": "member" } }

The built-in suppression guard has no file: it's always on and not editable, so a file for it could only ever be ignored.

sequences/<id>-<name>.yml

id: 2
name: Welcome
description: Three emails over the first week.
enabled: true
repeat_mode: once_per_email
steps:
  - id: 5
    email: emails/0012-welcome-aboard.md
    delay: { amount: 0, unit: days }
    status: active
  - id: 6
    email: emails/0013-where-to-start.md
    delay: { amount: 2, unit: days }
    status: draft

A readable ordered list, because reordering emails should be moving lines. Each step names one email file — that's where sharing is expressed. A step with no id is a new one.

emails/<id>-<subject>.md

---
id: 42
subject: Welcome aboard
pre_header: Here's what happens next
from: pat@example.com
labels:
  - onboarding
layout: Default
archived: false
---

Hi {{ person.first_name | default: "there" }},

Thanks for joining.

Frontmatter up top, markdown below — reviewing email copy should be reviewing a markdown diff. Compiled HTML never appears here: it's a cache Mimeo rebuilds. An email file with no id creates a new library row.

fields.yml, products.yml, settings.yml

# fields.yml
fields:
  - key: plan
    label: Plan
    type: text
    description: The person's current plan key.

# products.yml
products:
  - key: pro
    name: Pro

# settings.yml
tunables:
  quiet_buffer_days: { amount: 40, unit: days }
send_windows:
  rules:
    - label: pitch          # scope to emails carrying this label; omit for the global rule
      days: [1, 2, 3, 4]    # weekdays 0-6, Sunday is 0
      start: "11:00"        # "HH:MM", 24-hour, in the instance's time zone (UTC by default)
      end: "15:00"          # "24:00" closes the day
sending.mailing_address: "Example Co, 123 Main St, Anytown, ST 00000"  # what {{ mailing_address }} shows in footers

themes.yml carries the colours, font stacks and page shape your layouts are written against. Only the values a theme actually sets are written, not every variable resolved — so a diff shows the decisions somebody made, and a variable added to Mimeo later doesn't arrive as a change to every theme in every repo. For a theme the file lists, the file is the whole answer: a variable removed there is removed on the instance.

# themes.yml
themes:
- key: default
  name: Default
  default: true              # exactly one theme is the default
  variables:
    link_color:
      light: "#2563eb"       # every colour carries both schemes
      dark: "#93c5fd"
    content_max_width: 600px

Themes are applied before templates, because a layout's stylesheet is validated against the theme it names — so a theme and the layout referring to it can land in the same push. A theme on your Mimeo but not in the repo is left alone. The full variable vocabulary is on the Themes API page.

templates/layouts.yml carries the settings a layout's own two files can't: which theme its {{ theme.* }} tags resolve against, and which layout is the default. Entries name the layout by the file stem its .html and .css share. Omit theme and the layout follows whichever theme is the default; a theme that doesn't exist on your Mimeo or in the same push's themes.yml blocks the push, as does an entry naming a file the repo doesn't have — a typo silently doing nothing is the worst outcome for "apply this theme".

# templates/layouts.yml
layouts:
- file: 0014-builder-methods-layout
  theme: builder-methods     # omit to follow the default theme
  default: true              # exactly one layout is the default

A send-window rule gates when scheduled email goes out: exactly one rule applies to any given email — the first whose label the email carries, else the first rule with no label — and an email due outside its window is held until the next open slot rather than dropped. Malformed rules don't apply: push blocks them with the problem named, because a rule that stored broken would quietly hold sends. The full format and its checks: the Settings API.

A field's key is locked after creation — it's what every stored value and every condition names.

What the CLI doesn't touch

Only the paths above. Your README, CLAUDE.md, .claude/skills/, notes, and any dotfile inside a managed directory (a .gitkeep holding an empty folder open, say) are yours — never read, never removed.

Mimeo never touches git. The repo is yours; the CLI only talks to your Mimeo over HTTP.

See also: CLI · Definitions API · Flow schema