← Strategy on a page
This tab is the engine room: how decisioning and contact rules work, for whoever builds or audits a journey. Each journey pack's Reference tab carries the six binding rules; this page is the full picture.

Contact strategy & orchestration

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).

How the lifecycle engine works, end to end

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.

Warehouse data
already there: churn scores · RFM (recency, frequency, spend) · lifecycle stage · sales
Nightly dbt job
applies the journeys' authored rules
One decision table per customer
owning journey · next best action · treatment + payload
Nightly sync to Braze
the existing pipe, extended
Braze executes
caps, consent, quiet hours enforced
Bypass lane: real-time events
live auction · abandoned search · travel day
Straight to Braze event triggers
never wait on the batch; read the decision table for context only (persona, propensity)

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.

Already exists (confirmed in the warehouse)

  • Two churn-risk models, ~736k customers scored 0 to 100 against their own rhythm (confirmed); already synced to Braze
  • RFM, lifecycle stage, the business/leisure classifier
  • The Snowflake-to-Braze sync path (carries persona data today; we extend it, not build it)
  • The rules: ten journeys' decision tables, authored and approved, in Airtable and the journey packs

The four net-new builds (no new services)

  • journey_state: one field per customer = which journey owns them today. A dbt model; its rules are written and signed off
  • The decision table + its log: dbt models that apply the journeys' rules nightly and record every decision, so results trace
  • Extend the nightly Braze sync to carry the new fields, sized against Braze attribute rate limits before cutover
  • Schedule the churn model nightly: built, but last refreshed 14 Jul (confirmed); slip detection needs a daily score

Other fixes the journeys need (independent of the engine)

  • Content-card tracking: impressions record zero in the warehouse (confirmed), blinding measurement for five journeys. Highest-priority instrumentation fix
  • Trip-completed event: none exists; proxied by scheduled departure + 1 day. Fine to launch on, worth fixing
  • Deep-link reliability: universal links fail silently in the survey and referral flows; once Product scopes the personalised Home, one default landing contract for every journey's taps
  • Journey 3's two launch blockers: consent state and lead state at the bid seam. Per-journey data asks live on each pack's Engineering brief tab; the fortnightly sprint board is the system of record
Deliberately not yet: no decisioning microservice, no real-time decision API, no ML platform. Those start only after the measurement loop proves itself; stage 1 is dbt models on the existing stack plus a bigger nightly sync.
Where the rules are authored: the ten journey packs' decision tables, plus Airtable's Contact & channel rules and MoT Treatments tables (MoT = moment of truth) as the authoring home. The engine compiles them nightly.
Analytics working brief: the validation plan ↗: the holdout designs, the score-logging-at-freeze ask, the commissioned baseline table.
No journey launch waits on the engine: canvases launch on Braze-native logic plus the ruled exclusion segments; the engine takes over journey by journey as it lands.

Architecture reference: how one decision is made

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.

Next best action = f( CONTEXT × PROFILE × SIGNALS ) → backbone constraints → treatment · channel · timing · next journey → LOG
CONTEXT, where they are: set per moment by the journey map: the moment ID, the screen or surface, the journey stage, days to travel, and the active states (S1 travel imminent, S2 auction live).
PROFILE, who they are: slow-moving and refreshed nightly: lifecycle band, value tier, persona, bid history (never bid / bid lost / won before), transaction history, consent per channel, platform.
SIGNALS, what is happening now: real-time events only, never a stale overnight copy: session events, dwell on decision surfaces, the exit screen, auction status, disruption on the route, engagement recency.

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.

Every decision is logged: the context, the candidates considered, the winner, and what was suppressed and why. The log is the contact-pressure audit trail today and the training set the Q4 ranking model learns from; its execution half is the arbitration-log row on the scoping board.

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).

Segmentation & journey-state, the foundation

⚑ Proposed, the reference to ratify in a working session. PROPOSED: to ratify in a cross-team working session (Analytics + CRM + Eng + Braze). This block is the reference for that session; nothing here is signed.

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.

StageWindowWhat it means
Active0 to 60 daysTransacted or active within the window: the habit-loop / grow audience.
Lapsing61 to 90 daysSlipping: the cheap-save window (a save here is ~20% effective vs ~0.5% once churned).
Lapsed91 to 240 daysInactive but recoverable: behaviour-triggered win-back.
Churned240+ daysLong inactive: selective, trust-led win-back only; suppress the cold tail.
Proposed bands from the strategy, Analytics to sanity-check against the data and ratify. One agreed set so every journey targets the same people.

The "current-journey" flag (journey_state), how we stop conflicting messages

What it isOne field per user = which journey currently 'owns' them, so a user is never actively messaged by two conflicting journeys at once.Possible valuesOne of the 10 lifecycle journeys, or 'none'.How it's setWhen a user qualifies for more than one journey, the arbitration order (below) picks the winner, that journey sets journey_state; the others suppress. Refreshed real-time for mission states (travel/auction), daily for programme journeys.How the Braze developer uses itThe Braze developer reads journey_state as an entry/exit + suppression condition on each Canvas: a journey only admits a user if no higher-priority journey already owns them, and holds/exits them if one takes over. (Proposed model: confirm the exact Braze wiring with the Braze developer in the session.)StatusNet-new, Analytics specs it, Eng syncs it to Braze (daily batch fine).
Known issues: dropped_off_user is broken (flags everyone regardless of threshold), don't use it. journey_state does not exist yet.

Who wins when a user qualifies for more than one journey → the arbitration order below.

Principles

  1. The customer's active mission owns the comms slot, their live task (travelling tomorrow, bidding now) wins; our scheduled agenda waits.
  2. Suppression blocks us interrupting their mission: never blocks responding to what they initiate.
  3. Caps protect value, not police volume, behavioural recovery is never starved by a blast budget.
  4. Quiet hours apply to our clock, not theirs, their 5am train sets its own send time.
  5. One message wins: no moment stacks two interruptions.

Arbitration order: the layers

LayerWhatRule
L0 · OperationalTickets · payment · disruption · auction outcomesAlways sends. Uncapped. Quiet-hours exempt. Outside all arbitration.
L1 · State overridestravel_imminent (S1) · auction_live (S2)The active mission owns the marketing slot.
L2 · Programme tierWhen no state activePost-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 constraintsCaps · quiet hours · suppressionApply across everything below L0.

States & exceptions

StateWindowOwns the slotAlways pass
S1 · travel_imminentT-48h → T0 per tripThat trip's pre-travel / travel-day commsL0 · same-itinerary/return-leg offers · responses to a new user-initiated mission
S2 · auction_liveactive bid → close + 2h cool-offThat auction's real-time comms (outbid · knocked-out · T-30m · LA)L0 · same-itinerary bid/BUN · responses to a new mission
S1 ∩ S2both at onceSame trip: actionable > informational. Different trips: nearest travel date wins.-

Cap classes

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.

ClassCoversCapsStatus
A · OperationalAll operational and service messagesUncappedruled
B · Context-triggeredState 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 journey3/day ratified 28 Jul (a backstop, not a selector; counts only triggered marketing)
C · Scheduled programmeHabit · re-booking · reactivation · waves · referral~3/week · no two promo journeys same week3/week ratified 28 Jul (a backstop, not a selector; counts only scheduled programmes)
Flood-guard: max ~4 marketing sends/user/day across B+C combined: the ceiling that catches multi-journey stacking. 4/user/day ratified 28 July as a backstop, not a selector.
Session: Ends after 30 min inactivity; reopen <30min = same session (no repeat recovery); >30min = new session. The 30-minute session window was settled before the 28 July ruling and is unchanged by it.
Quiet hours: A exempt. B exempt only when anchored to the user's own clock (their 05:30 train → 04:30 pre-departure is wanted); un-anchored B holds to 08:00. C hard floor 23:00 to 06:00, user's local tz.
Quiet hours, the two tiers this page used to omit: an earned early window 06:00 to 08:00, where a scheduled send goes only when the user's own observed engagement supports it (persona is the cold-start prior: commuters with morning activity default early, leisure defaults to 08:00); and a wind-down 22:00 to 23:00, where no new journey entries start and only in-flight sequenced sends go. Travel-day sends are clamped to no earlier than 04:30 and no later than 45 minutes before departure (ruled 6 July); the clamp re-arms if the departure time changes.

Same-day priority order, when several comms qualify at once

Ruled 9 July. Higher tier wins the slot. A sell never leads on the highest-stakes day.

#Wins the slot
1Operational (disruption / safety)
2Reassurance & wayfinding (GRW-16 did-it-work · GRW-17 platform/coach)
3Commercial (Veezu taxi / any upsell)
Cap: THE PRE-TRAVEL PROMOTIONAL LIMIT: max 3 promotional pushes per 24h inside the pre-travel and travel window. Transactional confirmations (ticket/payment) are exempt from the promotional cap
When several comms qualify for one user's travel day, higher tier wins the slot; a sell never leads on the highest-stakes day. The daily triggered ceiling is the outer ceiling across every day; the pre-travel promotional limit is a tighter local limit inside it, so when both apply the pre-travel promotional limit binds and its sends also count against the daily ceiling. The order here is ruled (9 July); the daily ceiling value was ratified 28 July as a backstop, not a selector.

One lapse owner per customer

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.

WindowOwnerWhat everyone else must do
Days 0 to 60 of a slipJourney 8 (early lapse-save)Journey 8 replaces the 50-day calendar entry outright and suppresses reactivation until the day-60 hand-off.
Day 60 onwardsJourney 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 wavesJourney 9 must NOT own lapseAnyone inside a Journey 7 or Journey 8 episode is hard-suppressed from every wave. Wave membership never suppresses a triggered journey.
Always-on batchNobodyThe 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 journeysHand over, never holdOnce 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.
Contact questions, settled 27 to 28 July 2026
a. the 28 July contact ruling is RATIFIED, in the amended three-part form. ① The layered architecture is ratified. ② The six dial values are ratified as backstops, not selectors, with narrow scopes: the daily ceiling counts only triggered marketing, the weekly cap only scheduled programmes. A backstop stops the Nth message on a bad day; it never picks which message goes. ③ Arbitration is upgraded from last-action to next best action: the decision table picks the message, using a day-window lookback rather than the most recent signal alone. Already ruled and standing: the 9 Jul arbitration order and the 30-minute session window. (For the record: earlier versions wrongly carried the class-B daily value as RULED 9 Jul; it was open until 28 Jul.)
b. The two caps that shared the number 3 now have distinct names. The daily triggered ceiling is the standing class-B limit: 3 triggered marketing sends per user per day, every user, every day, across every journey (ratified 28 Jul as backstop, the 28 July contact ruling). The pre-travel promotional limit is a tighter local limit inside the pre-travel and travel window: max 3 promotional pushes per 24h, transactional confirmations exempt (ruled 9 Jul with the order). The daily ceiling is the outer ceiling; the pre-travel limit sits inside it. When both apply, the pre-travel promotional limit binds, and the sends it allows also count against the daily ceiling.
c. Journey 6’s three-touch episode is APPROVED (27 July 2026; the decisions log holds the record). The two caps govern different units, which is why both stand. Journey 8 interrupts on a lapse signal we inferred, so two touches then silence is the right restraint. Journey 6 responds to the customer’s own completed trip, and two of its three touches are fired by that trip rather than by our guess about it. Journey 8’s two-touch cap is unchanged and still signed.

The six dials, ratified 28 July

Signed in one pass, as backstops, not selectors: the decision table picks the message, the dials only stop the Nth one.

DialValueStatus
Daily triggered ceiling (class B)3/dayratified 28 Jul (a backstop, not a selector; counts only triggered marketing)
Class C scheduled/week3/weekratified 28 Jul (a backstop, not a selector; counts only scheduled programmes)
Flood-guard (B+C)4/user/dayratified 28 Jul (as backstop, not selector)
Session window30 min inactivityratified 28 Jul (as backstops, not selectors); the 30-minute window was already ruled and stands
S1 windowT-48hratified 28 Jul (as backstop, not selector); lead-time distribution check stays with Analytics
S2 cool-offclose + 2hratified 28 Jul (as backstop, not selector); typical auction duration TBC, CRM Manager and Product

Journey standards: the template every journey must fill before build

No template, no build. Every field below must be stated in the journey pack before a canvas is briefed.

FieldWhat it must state
EntryTrigger event + qualifying criteria + canonical segment(s)
Layer + cap classWhich layer its sends live in (L0/L1/L2) and which cap class (A/B/C)
Exit conditionsSuccess (converted) · timeout (max duration) · opt-out · state change (e.g. entered S1)
Re-entryMoment 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 consumedWhich 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)
CollisionsSame 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 arcSteps · waits · decision splits · message hierarchy, one job per touch. No specced sequence = not build-ready.
Experiment contaminationChecked against the live Test & Learn roadmap; any audience/timing overlap flagged to Analytics before launch
MeasurementHoldout + primary metric + guardrail, filed on the Test & Learn plan
OwnerNamed build + copy owners

Consent & preference

Marketing consent is PECR-compliant (the UK Privacy and Electronic Communications Regulations) and separate from transactional. Braze subscription groups per channel (email · push); global unsubscribe overrides everything in classes B and C. Permission forks route the treatment: email+push · email-only · push-only · none (in-app only). Opt-in is earned at the capture-ladder moments (ACT-15, ACT-12, ACT-13, ACT-24), never incentivised at peak moments.

Deliverability

Warm up any large dormant send gradually: blasting the ~820k churned base (assumed, Analytics to confirm) damages sender reputation. Sunset the genuinely dead rather than blast; the unengaged-tail suppression protects domain health. Complaint and bounce rates are standing guardrails.

Data & measurement

Signals the rules run on (all in the Engineering Requirements table): time_to_travel · auction/bid status · journey_state · canonical segment attributes · session state, each carrying its live / partial / needs-build status. Decision log: every won or suppressed decision is recorded: the contact-pressure audit trail and the training data for the ratified next-best-action table (28 Jul). Universal programme holdout ~5 to 10% (TBC, Analytics sizes) gives the always-on net revenue read. Freshness: state decisions (auction_live · travel_imminent) must consume real-time events, never daily attributes (a stale attribute suppresses marketing a day late); segment, persona and value attributes are fine daily. Contact-pressure dashboard: opt-out rate by frequency band + complaint rate + sentiment as standing CX guardrails.

Governance

Policy: Head of Marketing. Implementation: the CRM Manager (Braze frequency-cap groups, subscription groups, quiet-hours config). In-app arbitration: PMM co-owns. Measurement: Analytics. Change control: no journey ships without the journey-standards template completed plus a marketing-ops QA pass; policy changes are logged, signed decisions; reviewed quarterly. Role note: the comms-strategist and data-strategist roles are unfilled; the Head of Marketing and the CRM Manager operate this policy until the hires land (escalated, not absorbed silently).

Channel roles

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.

The cross-channel lifecycle timeline

How the moments sequence across channels through the lifecycle, the picture the arbitration rules above keep collision-free.

PhaseMomentWhat firesChannel · surfaceTimingTier
Auction / WaitACT-13In-lead opt-in askin-app slideupat bidCapture
Outcome (win)ACT-24 / GRW-01Celebrate + capture; enriched L0 win pushin-app + push (L0) + email receiptT+0Recognition
Outcome (loss)ACT-07Honest outcome + route back; supply-returns notifyin-app box + push/emailT+0 · D+3Recovery
Post-win 30dGRW-01Habit nudge (2nd bid)push/emailD+1 (IW) / D+3 (BUN) → D+15-16Habit
Pre-TravelGRW-16Day-before reassurance (never a sell)push + in-app stateT−22hReassurance
Travel DayGRW-17Wayfinding (platform/coach/seat)persistent state + T−3h push + LAT−3h → boardingWayfinding
Travel DayVeezu / TrainSwapTaxi / swap cross-sellpushT−12h / T−1.5hCommercial (yields to reassurance/wayfinding)