The one set of contact rules every journey obeys: states before tiers, caps by message class, quiet hours on our clock only, plus the cross-channel timeline showing how the moments sequence without colliding on one customer. Edit home: Airtable · Contact & channel rules (source of truth once signed). Cap dials below are ratified 28 Jul as backstops, not selectors (the 28 July contact ruling).
One home per layer: the global contact rules live on this page; each journey’s applications and exceptions live on its own review pack (the TO-BE map’s rules panel); unresolved arbitrations sit in the decisions log. The engine’s build requirements are tracked on the scoping board (rows tagged Lifecycle engine), scoped with Engineering on 30 Jul.
Ten journeys, one brain: a nightly table in the warehouse decides each customer's single next best action (the one message, or silence, we pick for them today), and Braze executes it. Built with dbt (the SQL modelling tool already running in the warehouse). Nothing here is new infrastructure.
One brain, two speeds. Slow decisions (who owns this customer, their next best action) are computed nightly in the warehouse. Fast moments (an auction closing, a train departing) stay event-triggered in Braze and never wait on the batch; they read the table for context only.
Working standard, DRAFT for ratification. This is the decision function every journey's decision table is authored to. The contact rules above are the constraint layer: they decide whether anything may send. This schema decides what sends, to whom, on which channel, and what happens next.
Five steps, fixed order. ① Eligibility gate: may anything send at all? Suppression, caps, active states and quiet hours rule first (the contact rules above); the verdict is serve, hold or queue. ② Targeting: which profile axes tailor at this moment (the discrimination rule below); only surviving axes create rows. ③ Treatment: the message and CTA per surviving cell, one job per touch, matched to the customer's barrier. ④ Channel arbitration: in-app first, push as the unseen fallback, email for considered content, forked by consent, never two channels doing the same job at once. ⑤ Timing: immediate, dwell-triggered, or anchored to the customer's clock, inside quiet hours.
The discrimination rule (the axis library). No customer axis earns rows by convention. Per moment, every library axis gets a verdict: tailors (it changes the message, timing or channel here, so it earns rows), same for all (considered, does not change treatment here, collapsed to one row with a one-line why), or not applicable. Typically one to three axes survive per moment; a table carrying every axis at every moment is unauthored. An axis enters the library only with band definitions, population sizes, evidence it discriminates, and its data status.
The road from rules to learning: Q3 runs this schema as authored rules executed in Braze. Q4 lets the decision log train a model that ranks candidate treatments inside the rules. The end state is real-time 1:1 arbitration across channels. At every stage the contact rules stay the guardrail the engine cannot override, and the build requirements stay the Lifecycle-engine rows on the scoping board (no new asks).
Lifecycle-stage cut-offs, how many days since a user last transacted defines their stage. Every journey targets off these, so we need one agreed set.
| Stage | Window | What it means |
|---|---|---|
| Active | 0 to 60 days | Transacted or active within the window: the habit-loop / grow audience. |
| Lapsing | 61 to 90 days | Slipping: the cheap-save window (a save here is ~20% effective vs ~0.5% once churned). |
| Lapsed | 91 to 240 days | Inactive but recoverable: behaviour-triggered win-back. |
| Churned | 240+ days | Long inactive: selective, trust-led win-back only; suppress the cold tail. |
Who wins when a user qualifies for more than one journey → the arbitration order below.
| Layer | What | Rule |
|---|---|---|
| L0 · Operational | Tickets · payment · disruption · auction outcomes | Always sends. Uncapped. Quiet-hours exempt. Outside all arbitration. |
| L1 · State overrides | travel_imminent (S1) · auction_live (S2) | The active mission owns the marketing slot. |
| L2 · Programme tier | When no state active | Post-win habit → re-booking → reactivation/defend → always-on. Tiebreak (upgraded, the 28 July contact ruling ratified 28 Jul): next best action via the decision table with a day-window lookback, not last action. |
| L3 · Global constraints | Caps · quiet hours · suppression | Apply across everything below L0. |
| State | Window | Owns the slot | Always pass |
|---|---|---|---|
| S1 · travel_imminent | T-48h → T0 per trip | That trip's pre-travel / travel-day comms | L0 · same-itinerary/return-leg offers · responses to a new user-initiated mission |
| S2 · auction_live | active bid → close + 2h cool-off | That auction's real-time comms (outbid · knocked-out · T-30m · LA) | L0 · same-itinerary bid/BUN · responses to a new mission |
| S1 ∩ S2 | both at once | Same trip: actionable > informational. Different trips: nearest travel date wins. | - |
The send class decides the treatment, never the platform (ruled 22 August). Every auction-linked send ships from Braze as service class: consent-free, uncapped, never suppressed by anything commercial. One guard: inside the final minutes before a close the native path still sends, because a status message landing after the close is a recorded harm. A second guard runs the other way: an open support complaint suppresses every marketing-class send until the complaint resolves, null-safe where the flag is missing so nothing waits on the feed. Service-class sends are exempt: auction status still reaches a customer with a complaint open.
| Class | Covers | Caps | Status |
|---|---|---|---|
| A · Operational | All operational and service messages | Uncapped | ruled |
| B · Context-triggered | State comms + session-responsive recovery (dwell/drop-off/abandoned-search/outbid/T-30m/LA) | 1 recovery/moment/session · max 2 same recovery/day · 1 recovery/auction · THE DAILY TRIGGERED CEILING: 3 triggered marketing sends per user per day, across every journey | 3/day ratified 28 Jul (a backstop, not a selector; counts only triggered marketing) |
| C · Scheduled programme | Habit · re-booking · reactivation · waves · referral | ~3/week · no two promo journeys same week | 3/week ratified 28 Jul (a backstop, not a selector; counts only scheduled programmes) |
Ruled 9 July. Higher tier wins the slot. A sell never leads on the highest-stakes day.
| # | Wins the slot |
|---|---|
| 1 | Operational (disruption / safety) |
| 2 | Reassurance & wayfinding (GRW-16 did-it-work · GRW-17 platform/coach) |
| 3 | Commercial (Veezu taxi / any upsell) |
A slipping customer is owned by exactly one journey at a time, so nobody gets two lapse messages in one episode. Triggered outranks scheduled; DEFEND outranks RECOVER. The journey_state field arbitrates, and it is one write with one owner so there is no second clock to drift.
| Window | Owner | What everyone else must do |
|---|---|---|
| Days 0 to 60 of a slip | Journey 8 (early lapse-save) | Journey 8 replaces the 50-day calendar entry outright and suppresses reactivation until the day-60 hand-off. |
| Day 60 onwards | Journey 7 (triggered win-back) | journey_state hands over at day 60 with a VIP-priority flag. The first-slip save never re-fires at day 60, so the same decay never gets both messages. |
| Scheduled waves | Journey 9 must NOT own lapse | Anyone inside a Journey 7 or Journey 8 episode is hard-suppressed from every wave. Wave membership never suppresses a triggered journey. |
| Always-on batch | Nobody | The midweek and weekly reactivation pushes, and the lapse canvas, must carry a journey_state exclusion segment before the new journeys go live. This is a launch precondition, not a nice-to-have (decision the decisions log, CRM + Data, open). |
| Post-trip and first-win journeys | Hand over, never hold | Once a customer goes quiet past their personal gap, journey_state flips and Journey 8's episode rules own them. First-win journeys exit to no-send, never to a legacy lapse timer. |
Signed in one pass, as backstops, not selectors: the decision table picks the message, the dials only stop the Nth one.
| Dial | Value | Status |
|---|---|---|
| Daily triggered ceiling (class B) | 3/day | ratified 28 Jul (a backstop, not a selector; counts only triggered marketing) |
| Class C scheduled/week | 3/week | ratified 28 Jul (a backstop, not a selector; counts only scheduled programmes) |
| Flood-guard (B+C) | 4/user/day | ratified 28 Jul (as backstop, not selector) |
| Session window | 30 min inactivity | ratified 28 Jul (as backstops, not selectors); the 30-minute window was already ruled and stands |
| S1 window | T-48h | ratified 28 Jul (as backstop, not selector); lead-time distribution check stays with Analytics |
| S2 cool-off | close + 2h | ratified 28 Jul (as backstop, not selector); typical auction duration TBC, CRM Manager and Product |
No template, no build. Every field below must be stated in the journey pack before a canvas is briefed.
| Field | What it must state |
|---|---|
| Entry | Trigger event + qualifying criteria + canonical segment(s) |
| Layer + cap class | Which layer its sends live in (L0/L1/L2) and which cap class (A/B/C) |
| Exit conditions | Success (converted) · timeout (max duration) · opt-out · state change (e.g. entered S1) |
| Re-entry | Moment comms fire on every event (every loss gets the loss response). Sequences never stack: one active sequence per journey per user; a new qualifying event restarts or updates it. Cooldowns apply only to scheduled re-solicitation, never to responding to a real moment. |
| Suppressions consumed | Which of the always-on suppression sets apply: active disruption on route · route-blocked · ~12-month unengaged tail · journey_state dedupe · per-auction recovery cap · power users (suppressed on the −11.8% holdout evidence, confirmed) |
| Collisions | Same event, same user, same window: one of three outcomes per moment. STACK is banned (two+ overlapping jobs landing together). REPLACE: the designed comm takes the slot, the legacy duplicate retires (one event, one message per channel). SEQUENCE: distinct jobs get a deliberate order, defined gaps, channel separation, including the unseen-fallback (companion push only if the in-app was not seen within N minutes). L0 receipts always send. |
| Sequence & message arc | Steps · waits · decision splits · message hierarchy, one job per touch. No specced sequence = not build-ready. |
| Experiment contamination | Checked against the live Test & Learn roadmap; any audience/timing overlap flagged to Analytics before launch |
| Measurement | Holdout + primary metric + guardrail, filed on the Test & Learn plan |
| Owner | Named build + copy owners |
Full mechanics library, limits and Native-vs-Braze build tags → Mechanic reference. Each journey's channel ladder lives on its pack's TO-BE map.
How the moments sequence across channels through the lifecycle, the picture the arbitration rules above keep collision-free.
| Phase | Moment | What fires | Channel · surface | Timing | Tier |
|---|---|---|---|---|---|
| Auction / Wait | ACT-13 | In-lead opt-in ask | in-app slideup | at bid | Capture |
| Outcome (win) | ACT-24 / GRW-01 | Celebrate + capture; enriched L0 win push | in-app + push (L0) + email receipt | T+0 | Recognition |
| Outcome (loss) | ACT-07 | Honest outcome + route back; supply-returns notify | in-app box + push/email | T+0 · D+3 | Recovery |
| Post-win 30d | GRW-01 | Habit nudge (2nd bid) | push/email | D+1 (IW) / D+3 (BUN) → D+15-16 | Habit |
| Pre-Travel | GRW-16 | Day-before reassurance (never a sell) | push + in-app state | T−22h | Reassurance |
| Travel Day | GRW-17 | Wayfinding (platform/coach/seat) | persistent state + T−3h push + LA | T−3h → boarding | Wayfinding |
| Travel Day | Veezu / TrainSwap | Taxi / swap cross-sell | push | T−12h / T−1.5h | Commercial (yields to reassurance/wayfinding) |