Theme

Definitions repo layout

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

The instance database is the runtime source of truth. The repo is the authoring surface: the things you write, in the format most natural to edit them in. mimeo pull refreshes the files from the instance; 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
fields.yml                          custom field definitions
products.yml                        product keys + names
settings.yml                        tunables, send windows, brand palette and buttons

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 the instance'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, brand.palette and brand.button_styles. Provider keys, storage keys and webhook secrets are entered in the admin UI and stay encrypted on the instance — 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 the instance'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 the instance 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",
  "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.

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: [email protected]
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 the instance 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: []
brand.palette: []
brand.button_styles: []

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 instance over HTTP.

See also: CLI · Definitions API · Flow schema