For everyone

· 8 min read

Your ESP is not the source of truth. Your repo is.

Every lifecycle program eventually produces the same artefact: a template in the ESP that nobody can fully explain. Somebody hardcoded a price in it. Somebody else fixed the mobile padding and did not tell anyone. The version history in the dashboard records that it changed on a Tuesday, which is not the same as recording why. This post is about moving the canonical copy somewhere that can answer the question, and to the four commands that do it.

Justin Williames

By Justin Williames

Founder, Orbit · 10+ years in lifecycle marketing

SharePostPost

The template nobody can explain

Open the email template your onboarding program has been sending for the last eighteen months. Somewhere in it is a decision you cannot account for — a hardcoded price that was right in March. Nearby, a module that exists in this template and nowhere else because someone needed it once at 6pm. Then the padding value patched to fix a Gmail render and never written down as a rule, so the next person patches it again next quarter, differently.

None of that is a discipline failure. It is what happens when the only durable copy of a program lives inside a tool built to send messages rather than to explain them. Your ESP — email service provider, the platform that stores your templates and actually delivers the mail: Braze, Klaviyo, Iterable, whichever — is very good at its job. Its job is delivery. It was never asked to hold the reasoning.

So the claim, up front: the canonical version of a lifecycle program should live in a repository you own. The ESP holds a derived copy pushed from it. Not a backup. Not a mirror you sync when you remember. A copy, downstream of a source, in the same sense that a compiled binary is downstream of code.

Two very different people arrive at this conclusion. One has run lifecycle for a decade and is currently maintaining four templates that have forked from the same original without anyone deciding to. The other founded a company nine months ago, sends four emails and has written none of it down anywhere. Same argument. Different volume.

Why the dashboard loses this job

The specific failure is that an ESP is not diffable, not queryable and not versioned in any way that survives a question. Three properties. Each one breaks something concrete.

Not diffable: you cannot see what changed between the template that worked and the template that clips in Gmail. The dashboard will show you both, one at a time, in a WYSIWYG editor that has already re-serialised the markup. Not queryable: you cannot ask "which of our emails still reference the old pricing page" and get an answer without opening them by hand. Not versioned in a useful sense: dashboards record that an asset changed and who was logged in. The reason lives in a Slack thread that scrolled away in 2025.

The wiki does not rescue this. It is worth being blunt about why, because most teams try the wiki first. Wiki prose drifts from what actually ships, silently — there is no build that fails when the documented header spec and the shipped header stop matching. A doc that can be wrong without anything breaking will eventually be wrong. Usually about the thing you most needed it to be right about.

The AI-era version of this problem is sharper. A model asked to build your next lifecycle email needs to load context cold: what your modules are, what your brand tokens are, what this program already promised the user, what was tried and rejected. If that context lives across a dashboard, a Figma file, three wiki pages and one person's head, the model re-derives it every session and gets it slightly wrong every session. Slightly wrong, at speed, is a worse failure mode than slow.

What canonical actually means — the four rules

"Single source of truth" is one of those phrases that survives on nobody checking it. The enforceable version is four rules, sitting at the top of the repo's README where every session reads them.

Git is canonical. If an AI or a human builds from it, it lives here. Every fact — a number, a colour token, a subject line, a rule about when the win-back series stops — has exactly one editable home. Other tools link to it or omit it. They never keep a second editable copy, because two editable copies is just drift with a delay.

The ESP is derived. The canonical HTML lives in the repo. What sits in Braze or Klaviyo is a snapshot pushed from it. If the two disagree, the ESP is wrong by definition. Defining it that way turns drift from a fact of life into a bug you can detect and re-derive away.

The graph is derived too. If you index the repo into a knowledge graph so a model can query it, that index is regenerated from the files and never hand-edited. The moment someone edits the index directly, you have two sources again and the newer one is invisible.

Comprehension is not enforcement. The repo helps a session find and record the right rule. It does not certify that an email renders correctly. Reading the docs is never permission to ship — that stays with the gate, which is the next section and the part most implementations skip.

1

Editable home per fact. Not one preferred home — one.

4

Rules that go at the top of the README, because a rule nobody reads is decoration.

0

Invented figures allowed past the gate. Stats come from a whitelist file or the module is dropped.

Four commands, and what each one leaves behind

The reason this usually stays an aspiration is that setting it up by hand is a fortnight of work nobody has. Orbit collapses it into four steps, run in this order, each leaving a real artefact on disk.

Scaffold the repo. orbit_bootstrap_brainwrites the shape: the four rules in the README, a conventions file that defines the frontmatter every knowledge file carries, a folder per lifecycle stage and stubs for the two standing logs — a decisions log and a workflow-learnings log, both append-only, both newest-first. The decisions log is the durable "why" that the dashboard could never give you.

Ingest what you already send. orbit_learn_email_templatetakes a real HTML email — one you have actually shipped — and turns it into a module catalogue plus brand tokens. This is the step that decides whether the whole exercise is worth anything. A design system derived from a vendor's template gallery describes a program you do not run. A design system derived from last month's onboarding email describes yours, including the bits you are not proud of, which are exactly the bits that need a rule written about them.

Generate the gate. orbit_generate_brain_gate writes an offline build script parameterised to your limits. More on it below — it is the load-bearing step.

Frame the programs. orbit_scaffold_brain_program creates one folder per program with a PRD stub inside it, carrying a human_approved field that starts false. An AI can research and draft into that file all day. Nothing gets built from it until a person reads it and flips the flag. That single boolean is the difference between a model that helps you write specs and a model that authors your program without being asked.

The gate is the difference between a design system and a folder of files

Here is where most "we put our emails in git" efforts die. The repo gets created, the first three programs get documented properly, then a deadline arrives and someone edits the ESP directly. Nothing fails. Nobody notices for five months.

A gate is what makes the difference. Concretely, the generated build/gate.sh compiles the source and then refuses to pass on a specific list of conditions: every templating branch resolved against one variable map rather than stripped, a byte-based clip check against Gmail's truncation limit, a no-horizontal-overflow check at a real emulated mobile viewport, an orphan check, an accessibility pass through axe-core with an email-specific allowlist, CTA parity so two buttons with the same visible label cannot point at different destinations and a diff between each module and the master template that fails on structural mismatch.

The related mechanism worth stealing even if you adopt nothing else: a verified-claims file. One markdown file that whitelists every statistic your emails are allowed to quote, each entry carrying the raw measured value, a display form rounded down so it stays true as the number grows, the query it came from and the date it was read. The build rule is unambiguous — a figure in a stat module either comes from that file or the module is dropped from the email. Not replaced with a placeholder. Dropped.

That is what converts "do not make up numbers" from a hope about your colleagues into something a script can fail on. Which, given that a marketing email with an invented statistic in it is a legal problem in several jurisdictions, seems like a reasonable thing to automate.

Same argument, different volume

If you have run lifecycle for years, the version of this you need is a migration. You already have the emails and the programs; what you do not have is a home for the reasoning, and the symptom is that your best template knowledge is currently a person. Start by ingesting the one template you send most, generate the gate against it and let the first failing build tell you which of your four forked headers is the real one.

If you have never run lifecycle and there is no team to inherit from, the version you need is a foundation. You have the easier job — nothing has drifted yet because nothing exists. Scaffold first, ingest the one email you already send even if it is a plain welcome message and write the PRD stub for the program you have not built. The stub with human_approved: false at the top is worth more than the polished doc you will not write.

Neither reader gets a watered-down version of the argument, because the argument does not weaken with experience. It gets louder. The practitioner has more to lose and more evidence; the founder has less to migrate and more time to get it right.

The one thing to do Monday: pick the single email your program sends most, find the most recent change made directly in the ESP dashboard and try to establish who made it and why. If that takes more than five minutes, you have found your answer about where the source of truth currently lives — and the honest answer is nowhere. Start with the Template Brain protocol, and if your stack is Braze, read the read-only-first connection guide before you give anything a write credential.

Read next

The Template Brain skill — the full protocol

Frequently asked questions

Does this mean I stop using the ESP's template editor?
For canonical edits, yes. The editor stays useful for previewing and for the last-minute fix you genuinely cannot avoid — but any change made in the dashboard has to be brought back into the repo the same day, or you have re-created the problem. The discipline is not 'never touch the ESP'. It is 'the ESP never holds the only copy'.
We are three people and send four emails. Is a repo overkill?
Three people is the cheapest time to do it, because there is almost nothing to migrate. The cost of a template brain scales with how much undocumented history you already have. Founders who set this up in month nine spend an afternoon on it; teams who set it up in year four spend a quarter.
What actually goes in the repo versus stays in the ESP?
The repo holds canonical template source, the module catalogue and brand tokens, one PRD per program with its copy and build specs, cross-program knowledge like naming conventions and audience-state definitions, the decisions log and the build and gate machinery. The ESP holds compiled snapshots pushed from the repo, plus everything that is genuinely runtime: audiences, schedules, delivery config.
How does this survive people editing the dashboard anyway?
It survives by readback, not by trust. After pushing compiled HTML you re-fetch what the ESP actually stored and compare bytes or a hash — a 2xx response is not evidence the body landed intact. When the stored version stops matching the repo, that is a detectable bug rather than an invisible fork.
Do I need the knowledge-graph layer as well?
Not to start. The graph makes retrieval faster once the repo is large enough that a model cannot hold it all at once. Build the repo, run it for a few programs and add the graph when finding things becomes the bottleneck. It is derived from the repo either way, so adding it later costs nothing you have already done.

This post is backed by an Orbit skill

Found this useful? Share it with your team.

SharePostPost

You finished the playbook. Get the next.

New guides and product updates land in your inbox when they ship. One list, real lifecycle work, unsubscribe the second it stops being useful.

Guides and Orbit updates only. No sequences, no selling your address.

Use this in Claude

Claude can run this playbook for you.

Orbit is a free extension for Claude Desktop — no licence key, no card — that runs the lifecycle work you just read about. You've read how it works; Orbit hands Claude the same playbook as a skill it can execute: discovery, build, QA, push, on your own ESP.

Download Orbit — free