Point it at your stack

· 8 min read

The handoff packet: seven parts, and the build question each one kills

You send the plan on Friday. The builder starts Monday. By Tuesday afternoon there are eleven questions in a thread, and the problem is not the eleven — it is the four they did not ask. A builder who cannot find the delay value in your document does not stall. They pick one day, because one day is reasonable. Now your program contains a decision nobody made and nobody remembers making. That is what a bad handoff produces: not delay, drift.

Justin Williames

By Justin Williames

Founder, Orbit · 10+ years in lifecycle marketing

SharePostPost

The questions after the handoff are the score

Score your last handoff honestly. Not by length — by the thread. Every question the builder asked was a hole in the document — and every hole they did not ask about was filled by their judgement instead of yours. Their judgement is usually fine. It is just not recorded anywhere, which means in four months when someone asks why the second reminder waits 72 hours, the answer is a person who has since moved teams.

The target is zero questions. It is achievable more often than people think, because the questions are predictable. A builder implementing a lifecycle program — the sequence of automated messages a customer receives as they move through onboarding, activation, retention — asks the same set every time, in the same order. Timing, audience, exit condition, which asset, which version of which asset, what counts as a pass. Write those down in a fixed shape and the thread goes quiet.

The reason this keeps failing is not laziness. It is that the person who designed the program does not experience the missing information as missing. You know the reminder waits three days because the support-ticket data said day four is too late. That fact lives in your head next to the delay value, so when you write "3 days" the reasoning feels present. It is not on the page. The builder gets the number without the constraint. The first time someone proposes tightening it to 24 hours there is nothing in the document to argue with.

Each part, and the defect it prevents

Orbit's build-packager protocol requires seven parts before it will call a bundle complete. The list is not arbitrary — each part exists because a specific class of build defect traces back to its absence.

  • Canvas build sheet.A Canvas is Braze's multi-step flow — the branching structure a user walks through. The build sheet is every step in order with its type, its delay, its audience filter and its exit condition. The defect it prevents: a builder inferring the branch logic from a diagram. Diagrams do not carry exit conditions. The exit condition is the part that decides whether an activated user keeps getting nagged.
  • Email asset manifest. Which HTML file, or which template ID, goes in which step. Named once, unambiguously. The defect it prevents is the one where two emails have nearly the same name and the wrong one goes live in the branch nobody checks.
  • Content Block manifest. A Content Block is a reusable snippet stored once in Braze and referenced by many templates — a footer, a legal line, a promo strip. The manifest says which blocks the program depends on and whether each already exists. The defect it prevents: a builder copying a footer inline because the block was not mentioned, which is how one program ends up with a legal footer that no longer updates with the others.
  • Personalisation snippet list.Every piece of Liquid — the templating language Braze and Klaviyo both use to insert customer data — written out with its fallback. Not "personalise the first name". The literal snippet, including what renders when the attribute is empty, because empty is a state a large share of your file is in.
  • Naming conventions. The convention itself, not a reference to one. A builder who has to go and find the convention will approximate it. An approximated convention is worse than none, because now the search that was supposed to return one program returns two spellings of it. The naming conventions guide covers the scheme; the packet carries the applied version, with the actual names spelled out.
  • QA checklist.What must be true before this goes live, phrased as checks with verdicts rather than areas to look at. "Review the rendering" is not a check. "No tap target under 44×44 CSS px at 390px width" is.
  • Test-user checklist. Named test profiles and the attribute state each one holds. More on this below, because it is the part that gets cut first and costs the most.

Seven is a lot to write by hand, which is exactly why handoffs arrive as a Figma link and a paragraph. That excuse expired. Orbit's Braze Build Packager skill takes the plan and the assets you already have and emits the seven parts as a bundle. The work left for you is the part only you can do: the one-line reason under each decision.

The part everyone skips: which version the step is bound to

Here is the failure that survives even a careful handoff. A Braze Canvas message step does not reference a template — it holds a copy of that template's content from the moment it was bound. Edit the template afterwards and the step keeps sending what it captured. The template list shows your fix. The Canvas sends the old one. Nothing in either interface tells you the two have diverged.

Which means "use the welcome template" is ambiguous the moment anyone touches the welcome template again. Every handoff written that way carries a slow leak. The packet has to name the version. It also has to say what happens on a later content change: the step gets updated directly, not the template, or the step gets rebound and re-verified. Write that instruction into the asset manifest, once, in the imperative.

The general rule underneath both of these: a check that cannot come back false is not a check. If the builder can tick it without doing anything, you have added a line to the document and nothing to the program.

Test users belong in the handoff, not in QA

The test-user checklist is the part that gets cut, and cutting it is how a program ships tested in exactly one state. Someone loads their own profile, the email looks right, it goes out. Their profile holds one combination of attributes. If the template branches on three flags, that profile represents one of eight possible emails. The seven it does not represent are the ones that ship unseen.

So the packet names the profiles. Not "test with a few users" — a table of external IDs with the attribute state each one is pinned to, one row per branch the program can take, including the states you consider unlikely. Unlikely states are where the defects live, precisely because nobody looks at them. The majority state is self-testing: everyone sees it, so a bug in it surfaces the same day. A rare arm can be broken for a quarter before a support ticket connects it back to a send.

Setting those profiles up is a job. It is also a job that only needs doing once per program and is reusable across every subsequent iteration, which makes it one of the highest-return items in the whole bundle. Pair it with a static enumeration of the branches — the Liquid branch coverage skill derives the full state space from the compiled HTML — and you get both halves: proof that every arm renders, plus live profiles that prove the data behind the arms is real.

The second reader forgets everything

Everything above would have been true in 2019. Here is what changed. A model is now the packet's second reader.

If Claude drafted the copy, planned the branches, or generated the Liquid, the working context for that program lives in a session that ends. Three months later you open a new one and ask to extend the program — add a branch, swap a module, adjust the timing for a new market. The model reads whatever artefacts exist. If the artefacts are eleven emails and a Canvas diagram, it will re-derive the reasoning from scratch, arrive somewhere plausible and build something internally consistent with a rationale you never chose. This is not a model failure. It is the same failure the human builder had, with a shorter memory and more confidence.

That changes one thing about how you write the packet: the one-line reason under each decision stops being documentation hygiene and becomes the actual interface. "72 hours, because support tickets spike from day four" is a constraint a model will respect and reason from. "72 hours" is a number it will happily optimise away. Constraints travel. Values do not.

It also raises the bar on where the packet lives. A handoff pasted into a Slack thread is gone. A handoff committed as files next to the assets is context the next session can read. So can the next builder, and you in March when you cannot remember why the Free branch skips the second email. Orbit's brain tools exist for this reason — the program artefacts sit in a repo, versioned, with a gate that runs before anything ships.

The one thing to do Monday: take the last program you shipped and write down its delay values, suppression rules and exit conditions from the live build, not the plan you wrote before it. The gap between those two documents is your handoff debt. It is usually larger and stranger than anyone expects.

Read next

Braze Build Packager — the skill that emits the packet

Frequently asked questions

Is a handoff packet overkill for a two-email flow?
The seven parts scale down — a two-email flow has a two-row build sheet and a two-row asset manifest. What does not scale down is the test-user checklist and the named template versions, because a small flow branches just as readily as a large one. If the flow has no branches and no personalisation, a build sheet plus a QA checklist is genuinely enough.
Who owns the packet when the builder is an agency?
You do. The packet is the contract. An agency builds what the document says and reasonably charges for anything the document did not say. The economics point the same direction as the craft here: every question they have to ask is billable ambiguity.
What if the plan changes mid-build?
Change the packet first, then the build. It takes an extra few minutes and it is the only thing that stops the document and the live program diverging on day one, which is the state most lifecycle programs are already in.
Does this apply outside Braze?
The parts do. The vocabulary changes — Klaviyo calls the flow a Flow, Iterable calls it a Journey, Content Blocks become snippets or saved blocks — and the version-binding behaviour differs per platform, so check whether your ESP snapshots template content into a step or references it live. That single question decides whether your asset manifest needs a version column.

This post is backed by an Orbit skill

More in Point it at your stack

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