Stories · Season 3 — Magpie · Episode 04

The Onboarding That Got Lost in If-Statements

In which signing up for a bank account turns out to have eleven steps, six of them optional, and one boolean too many.

The story

Opening a Magpie account looked simple to the customer: a few screens, take a selfie, done. Behind the scenes it was a gauntlet. Verify the email. Run identity checks. Wait for the document upload. Run sanctions screening. Maybe ask for proof of address, but only for some countries. Approve, or refer to a human, or reject. Send a welcome pack. Each step could succeed, fail, time out, or arrive out of order.

Magpie modeled this the way most teams start: a row with a pile of boolean and nullable columns. email_verified, id_checked, docs_received, sanctions_passed, address_needed, approved_at, referred_to_human. The onboarding logic was a sprawling method that read all of them and tried to work out, from the combination, what to do next.

It became unmaintainable in the way these things always do. Nobody could say with confidence which combinations of flags were valid. Customers got stuck in states that shouldn’t exist — approved_at set but sanctions_passed still false, because two callbacks raced. Owen, in compliance, asked the question that should be easy and wasn’t: “Can you show me, simply, every path from ‘started’ to ‘approved,’ and prove there’s no way to get approved without sanctions screening?” Kemi could not, because the “process” wasn’t a thing anywhere — it was an emergent property of forty if-statements.

Why this is hard the traditional way

A long-running business process — onboarding, a loan application, an order fulfillment — has states and allowed transitions between them. But modeled as a bag of flags, the states are implicit: they exist only as combinations of columns, and most combinations are nonsense that the code must defensively guard against forever. The “what happens next?” logic is scattered across every place that reads the flags, so the process has no single definition you can read, test, or show an auditor.

And because nothing enforces the transitions, illegal jumps happen. A late callback flips a flag, two events race, and an entity lands in a state the business swears is impossible — except it just happened, in production, to a real customer who’s now stuck.

How Relay changes the ending

Relay’s state-machine sagas let you declare the process as a state machine: the states, the events that move between them, and what happens on each transition. The process becomes one readable definition instead of a fog of flags.

Initially(
    When(ApplicationStarted).Then(BeginIdentityChecks).TransitionTo(VerifyingIdentity));
During(VerifyingIdentity,
    When(IdentityVerified).TransitionTo(Screening).Then(RunSanctionsCheck),
    When(IdentityRejected).TransitionTo(Rejected));
During(Screening,
    When(SanctionsPassed).TransitionTo(Approved).Then(SendWelcomePack),
    When(SanctionsFlagged).TransitionTo(ReferredToHuman));
// You literally cannot reach Approved without passing through Screening.

Now Owen’s “impossible” question is trivial: the path from started to approved is right there, and there is no transition into Approved that doesn’t come through Screening. Illegal states aren’t guarded against — they’re unreachable. Timeouts and out-of-order events are handled by the machine, not by defensive flag-checking. The process finally exists as a thing you can point at.

What it costs you to ignore this

  • The process lives nowhere. A workflow spread across flags and if-statements can’t be read, reviewed, or proven correct.
  • Impossible states become support tickets. Without enforced transitions, races and late callbacks strand real customers in invalid limbo.
  • Compliance can’t get answers. “Prove approval always requires screening” should be a five-minute question, not a code archaeology project.
  • Every change is risky. Adding a step to a flag-soup workflow means re-reasoning about every flag combination, forever.