# 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](/content/docs/manager-repo/index.html) holds the things you _write_, in the format most natural to edit them in — its definition files, specified here. [`mimeo pull`](/content/docs/cli/index.html) 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](/content/docs/cli/index.html). 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:

- **`pull` never merges two emails into one file** because their contents happen to match. Identical copies are a deliberate state; collapsing them would silently convert independent copies into one shared source, and the next edit would reach sends you never intended.

- **`push` never silently re-points a holder.** Pointing a step at a different email file is the same decision as the UI's chooser, so it appears in the diff in those words — "`10-send-pitch` now uses `emails/0043-x.md`, shared with 3 other places" — rather than as an anonymous id change.

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](/content/docs/usage/emails.html#used-in/index.html) 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](/content/docs/reference/flow-schema.html). `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](/content/docs/api/themes.html) 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](/content/docs/api/settings.html#send-windows/index.html).

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](/content/docs/cli/index.html) · [Definitions API](/content/docs/api/definitions.html) · [Flow schema](/content/docs/reference/flow-schema.html)
