← StrategyJourney 4 campaign brief
Journey 4, Engineering briefPack version: 15 Oct 2026, 10:39 (London)
Leave feedback
The Engineering brief

What Journey 4 asks Engineering to build, and the one hard rule

🔧 Open the Sprint 1 NOW board
The board is the system of record: live rows, owners, status, and each row's today-vs-to-be.
The one hard rule: no upgrade promise we have not checked. The close email's buy-now branch sends only where the whole party can be seated (pin D10). Changed 16 Sep 2026: the close screen's buy button stays as today; the card's words carry the honesty; no hide-the-button build.
1The requirements

The native pushes, out of the app: which stay native, and which move to Braze

All 21 sends the app fires itself, out of the app, each with its call: 10 move to Braze, 5 stay native, the rest retire, need a decision or belong to another journey. These calls are this pack’s proposals, to be agreed with Engineering.

The sendWhen in the journeyThe callWho does the work
Auction opens soon, bid placed
Native Push
Before bidding opensNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
Five minutes before bidding opens, once per auction.
Why, in one line
A scheduled nudge carrying no live figures, so a small send lag is harmless.
What decides or enables it
The auction open-time reaching Braze, which is a new feed.
Auction opened, outbid
Native Push
The moment bidding opensNATIVE → BRAZEBRAZE DEV
▸ What fires it, and why
What fires it
The customer’s pre-auction bid has already been beaten at the open.
Why, in one line
One of the two ruled outbid moves: Braze sends it as a service-class message with the position riding as event properties, and the final-minutes native guard applies.
What decides or enables it
Nothing new: it rides the same event the mid-auction outbid uses.
Knocked out on open
Native Push
The moment bidding opensNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
The auction opened and the customer’s bid asks for more seats than remain, so it can never win.
Why, in one line
Already the plan for the knocked-out moment: once the event reaches Braze, copy leaves the backend, the send becomes testable and it counts against the caps.
What decides or enables it
The knocked-out event with party size and remaining inventory, which is a new event.
Knocked out, mid-auction
Native Push
While the auction runsNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
Remaining inventory drops below the seats the customer’s bid needs, typically because a buy-now purchase or an instant win consumed seats.
Why, in one line
A persistent state, not a live figure, and its event carries party size and remaining upgrades, so the copy can be personal and can name the cause.
What decides or enables it
The knocked-out event, which is a new event.
Outbid, mid-auction
Native Push
While the auction runsNATIVE → BRAZEBRAZE DEV
▸ What fires it, and why
What fires it
Live on both generations. Sent to the single customer who lost the lead.
Why, in one line
Moves to Braze as a service-class send: the platform does not decide whether a message is marketing, the send class does.
What decides or enables it
The bid-rejection wording defect is separate and stays a Phase 1 fix. Latency stays on the Engineering audit path.
Time left, losing
Native Push
The final minutes before the closeNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
Five minutes before close, when the customer’s bid is not highest. The same event also drives the last-chance email.
Why, in one line
Already the plan: the branded last-chance alert replaces this push and is fired from the close-time feed.
What decides or enables it
Two new feeds: the auction close time, and whether the customer is currently winning.
Time left, winning
Native Push
The final minutes before the closeNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
Five minutes before close, when the customer holds the highest bid.
Why, in one line
Rewritten to a neutral in-the-lead note and moves to Braze as a service-class send and in the last few minutes before a close the app still sends its own push as the rest of the T-30 family.
What decides or enables it
The close-time feed for the branded Phase 2 replacement.
Auction ended, loser
Native Push
At the closeNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
The auction closed and the customer’s bid did not win. Auctions on the newer system also send it to a winner whose upgrade terminally failed, because the code treats every upgrade failure as an auction loss.
Why, in one line
Phase 1 strips the sell with the sender unchanged; Phase 2 moves the send to Braze on the already-funded close-cause event, so one system owns the close and its caps.
What decides or enables it
The close-cause event reaching Braze, and the delivery-certainty read before cutover.
Closed early
Native Push
At the closeNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
The auction was terminated before its scheduled close: buy-now sold out the inventory, the service was cancelled, an operator cancelled it, or a system failure closed it.
Why, in one line
Folds into the Braze sorry-message family as its early-close variant, service class, with the native send switched off in the same release.
What decides or enables it
The early-close trigger reaching Braze (rides the close-cause event if no existing event serves), and the same delivery-certainty read as the close push. If the trigger waits on Phase 2, the interim is the Phase 1 native rewording.
Upgrade timed out
Native Push
When a payment failsNATIVE → BRAZEBACKEND ENGBRAZE DEV
▸ What fires it, and why
What fires it
The ten-minute payment-fix window expired without a successful retry and the upgrade went to someone else. The scheduled task is cancelled if the winner fixes payment in time.
Why, in one line
The message is needed and the newer system does not send it.
What decides or enables it
A payment-window-expired event reaching Braze, which is a new event. This is a gap in the estate rather than a migration of something that works.
Auction opened, winning
Native Push
The moment bidding opensSTAYS NATIVENo work
▸ What fires it, and why
What fires it
Live on both generations.
Why, in one line
It tells the customer they are in the lead right now, and that fact can change second to second; only the app's own real-time state can send it without the risk of telling someone they lead when they no longer do.
What decides or enables it
Nothing.
The lock-screen status stream
Live Activity (iOS)
While the auction runsSTAYS NATIVENo work
▸ What fires it, and why
What fires it
Live on auctions run by the newer auction system only. Three message kinds: start, update, end.
Why, in one line
It is a state stream, not a message: the app renders it and the data must be live.
What decides or enables it
Nothing to move. The absent opt-out is the thing to decide: it is the only customer-facing surface in the estate with no preference check of any kind.
Instant win payment failed, card unusable
Native Push
When a payment failsSTAYS NATIVENo work
▸ What fires it, and why
What fires it
Live on auctions run by the newer auction system only.
Why, in one line
Real-time and service-critical, as the retryable variant: the bid has already been rejected and the customer needs to know now.
What decides or enables it
Nothing.
Instant win payment failed, retryable
Native Push
When a payment failsSTAYS NATIVENo work
▸ What fires it, and why
What fires it
The customer’s bid hit the instant-win price mid-auction, the immediate charge failed with a retryable decline, and the bid was rejected. The customer must bid again.
Why, in one line
Real-time and service-critical: the bid has already been rejected and the customer needs to know now.
What decides or enables it
Nothing.
Update your payment details
Native Push
When a payment failsSTAYS NATIVENo work
▸ What fires it, and why
What fires it
The customer won, the winning charge failed, and a ten-minute window is open to fix payment and keep the upgrade.
Why, in one line
A ten-minute recovery window is the least forgiving latency in the episode.
What decides or enables it
Nothing to move. The silent iPhone arrival, the push copy that names a different screen, and the dead-card wording are defect fixes for the sprint board.
Payment failed at the open
Native Push
When a payment failsDECIDE FIRSTBACKEND ENG
▸ What fires it, and why
What fires it
Three variants. Two are dormant capability, firing only for standard auctions on the older system whose continued existence in production is unconfirmed. The third is dead code.
Why, in one line
Two variants may already be dead in practice and the third certainly is.
What decides or enables it
A runtime check of whether standard auctions on the older system still exist. Until then nothing should be built on it.
Auction opens soon, no bid
Native Push
Before bidding opensRETIREBACKEND ENG
▸ What fires it, and why
What fires it
Orphaned. The template exists in the delivery service, the message bus admits it, and both apps have working handlers, but no code anywhere publishes it.
Why, in one line
Deliverable machinery with no producer.
What decides or enables it
Removal, or a decision to revive it as a Braze send.
Auction ended, loser, buy-now available
Native Push
At the closeRETIREBACKEND ENG
▸ What fires it, and why
What fires it
Dormant capability. It fires only for standard auctions run by the older system.
Why, in one line
The pack already retires the sell at the close, and the newer system has effectively retired this variant anyway.
What decides or enables it
Deleting it, which is the Phase 1 backend change.
Auction opened, milestone winner
Native Push
The moment bidding opensANOTHER JOURNEYNo work
▸ What fires it, and why
What fires it
Live on both generations.
Why, in one line
A win moment, which Journey 1 owns.
What decides or enables it
Journey 1 to make the call.
Auction opened, no bid
Native Push
The moment bidding opensANOTHER JOURNEYNo work
▸ What fires it, and why
What fires it
Live on both generations. Included for completeness: a watcher with no bid has not entered this journey.
Why, in one line
It belongs to the pre-bid funnel, which is Journey 3’s.
What decides or enables it
Journey 3 to make the call.
Auction ended, winner
Native Push
At the closeANOTHER JOURNEYNo work
▸ What fires it, and why
What fires it
Live on both generations. Auctions on the newer system also fire it immediately for instant wins mid-auction.
Why, in one line
A win moment, which Journey 1 owns.
What decides or enables it
Journey 1 to make the call.

One rule across the moved sends: a trigger that fires in the final minutes before a close is sent by the app, not Braze, so a delayed message can never land after the auction has closed. The release work sits on the switch-off row below.

16 rows: 7 data points, 5 sends moving into Braze and 4 computed traits (Analytics builds, no engineering hours). The D badge is the walkthrough pin number. Delivery fixes and app defects follow in their own sections.

Events, feeds and data points

The data this journey needs from the auction back end. The contract is the row; the surfaces that read it are listed, never restated.

The data pointWhat exists todayThe workWhat it must carryPhase · status · depends on
D1Why the auction ended
close_reason on the close event
Send why the auction ended, with the close, in time for the message to use it.
PARTLY LIVE IN BRAZE
The close reason was added to the close event and routed to Braze on 9 Sep 2026 (GRO-1896); a test-profile check must still confirm Braze receives it.
New event data on the closeEvery ending emits a readable reason: won by someone else, sold out, failed, ended early. Sold-out is its own value, two branches key on it. A null or unreadable reason renders the safe generic frame, never a loss message. Streams to Braze, because it triggers the close card’s copy layer and the failure sends. Carries the recovery facts the day-3 send segments on: first loss or repeat loss, buy-now availability, time to travel.
Used where: The close screen’s card; the native close push; both close emails; the failure sends
PHASE 1
ships now
Shipped; Braze receipt to confirm
The close-reason check on a test profile (Lisa or Lewis).
D7The knocked-out event
knocked_out + party size + remaining upgrades
LIVE IN BRAZE
Live since 8 Sep 2026 (GRO-1897), with party size and remaining upgrades.
New real-time eventWhy this bidder is out when raising the bid cannot fix it, with the party size that knocked them out and the remaining upgrade count, so the message can be honest about both.
Used where: The knocked-out push; the close-screen wording for that customer
PHASE 1
ships now
Live
Analytics are sizing how many knock-outs are group-size; a mostly-group-size answer makes this a Product inventory question before it is a message.
D10Availability for the whole group
party-size availability + buy-now flag
Send the buy-now close email only when the whole party can be seated; otherwise the normal loss email.
LIVE IN THE BACKEND
In production, Lewis confirmed on 30 Sep 2026 (GRO-1861); Linear still shows it in testing.
  • Back end exposes
  • gates the close email’s buy-now branch
Availability for this party size, tested when the email renders, plus whether that train carries buy-now. Changed 16 Sep 2026: the screen’s button stays; the card’s words carry the honesty.
Used where: The buy-now close email
PHASE 1
ships now
Live, pending Linear
Nothing.
D13What the upgrade went for
seat count + final price at the close
LIVE IN BRAZE
Live since 8 Sep 2026 (GRO-1899): seat count and final price reach Braze on the auction loss events.
New event data on the closeWhat the upgrade actually sold for, so the close screen shows a real comparison that anchors the next bid instead of a vague one.
Used where: The close screen’s comparison
PHASE 2
gated
Live
The close-screen half sits with Product, not CRM.
D5The auction close time
close-time live feed
LIVE IN BRAZE
Live since 10 Sep 2026 (GRO-1900).
New real-time feedThe close time as a live feed, shared with the pre-travel journey.
Used where: The thirty-minutes-left alert; the lock-screen countdown
PHASE 2
gated
Live
Nothing.
D6Is this customer in front
is_currently_winning
LIVE IN BRAZE
Live since 10 Sep 2026 (GRO-1900) as a send-time Braze lookup, not a stored attribute.
New real-time eventIn front right now, so the thirty-minutes-left alert never tells the leader they are losing.
Used where: The skip-if-winning suppression on the thirty-minutes-left alert
PHASE 2
gated
Live
Nothing.
D12The tell-me-when-it-is-back tap
route alert + original travel date
Save the route alert and travel date when the customer taps tell-me-when-it-is-back.
BUILDABLE TODAY
Route alerts already send through Braze (Engineering, 25 Aug); verify the existing alert-matched event covers alerts saved from this tap.
App-side write, rides the close-screen buildRoute plus the original travel date, written to the profile on the tap, so the supply-returns send is scoped to their route and never widens.
Used where: A route watch (on hold); the supply-returns send, on hold until user research
PHASE 1
ships now
Submitted
Rides the close-screen build; verify first, build only if the check fails.

The engineering work to move native sends into Braze

No new data in this band: logic, integration and release work.

The changeThe workPhase · status · depends on
One message at the close
the two close emails become either/or
Stop the double email at the close: one branch becomes either/or.
Logic change, one branchPHASE 1
ships now
Ready to brief
None.
▸ Where it stands, the detail and where it is used
Where it stands
NO NEW DATA
A live defect, fixable now.
The detail
The branch exists: isBuyItNowAvailableAfterServiceDeparture in AuctionEngineNotificationService.php:416. Make it either/or, matching the legacy if/else at InternalController.php:325-334.
Used where: The two close emails: auction closed, and buy-now available
The close card displays Braze’s words
front-end integration, once
Connect the close-screen message card to Braze.
Front-end integrationPHASE 1
ships now
Needs a roadmap slot
Product schedules it; the words-move-to-Braze decision is made.
▸ Where it stands, the detail and where it is used
Where it stands
NO NEW DATA
One condition before build: the card must refresh fast enough to be state-accurate; caching lag is why this class of surface went native before (Design and CRM, 25 Aug).
The detail
The app builds the card once, natively, with fallback wording for every ending baked in; Braze’s personalised words replace it when they arrive and are fresh. Never blank, never stale. After this, every wording change ships from Braze with no app release.
Used where: The close screen’s message card
Switching the app’s own sends off
release logic, per cutover
Switch each native auction send off in the release where Braze takes it over.
Release logicPHASE 1
ships now
Submitted
Rides each Braze cutover; without it the moment double-sends.
▸ Where it stands, the detail and where it is used
Where it stands
NO NEW DATA
Engineering, 25 Aug: the full native-to-Braze move is a very large piece of work; it lands send by send, per cutover, never as one release.
The detail
One exception: in the last few minutes before a close the app still sends its own push, because a message that lands after the close is worse than none.
Used where: The outbid pair; the thirty-minute family; the knocked-out pair; the early-close send
The dormant buy-now close push
delete the dead variant
Delete the dormant buy-now variant of the close push, so only the losing message can fire.
Deletion, needs a releasePHASE 1
ships now
Ready to brief
None.
▸ Where it stands, the detail and where it is used
Where it stands
NO NEW DATA
Dead code today, on our reading.
The detail
Verify first which variant the newer auction system actually selects: our reading is inference, not proof.
Used where: The native close push
Two reworded pushes
finished copy into a release
Ship the reworded outbid push and the in-the-lead note when Design has ruled.
Copy into an app releasePHASE 1
ships now
With Design
Design first.
▸ Where it stands, the detail and where it is used
Where it stands
NO NEW DATA
Nothing to do yet.
The detail
Each arrives as finished copy, never as a choice.
Used where: The native outbid push; the in-the-lead push

New data request · added 5 Sep 2026

One ask: the nightly warehouse-to-Braze sync carries three new loss-history traits from Iain and Ben’s service categorisation analysis. Analytics owns the traits; their contracts live on the Analytics brief. Phase 2; Phase 1 ships without them.

The askWhat exists todayThe workWhat it must carryPhase · status · depends on
Three loss-history attributes on the nightly sync
sync payload extension
New data request, added 5 Sep 2026 (Iain and Ben's analysis). Analytics defines and computes the three loss-history traits (prior-loss count, loss causes counted, late-bid losses flagged and counted); the contracts live on the Analytics brief. The ask here is only that the nightly sync carries them, the same scoping Journey 8 opened.
DOES NOT EXIST YET
Extend the daily warehouse-to-Braze sync payload by three attributes; no new pipeline and no new events.The three attributes, nightly, at send time.
Used where: The loss sends; Journey 5's coaching
PHASE 2
gated
Phase 1 ships without it
Rides Journey 8's sync scoping (Sophie, with Olu and Sharla).
▸ The 3 computed traits this journey reads: Analytics builds them, no engineering hours; and what is not listed, and why

Computed traits this journey reads · Analytics builds, no engineering hours

Named here so the full picture is one table; the contracts live on the Analytics brief tab, which is their home.

The computed traitWhat exists todayThe workWhat it must carryPhase · status · depends on
Returned since the close
returned_within_7d
LIVE IN BRAZE
Built by Analytics and syncing to Braze since 30 Sep 2026 as three fields: last_competitive_loss_at, last_competitive_loss_auction_id and days_to_return_post_loss (-1 until a return). Returned means an upgrade search, a trip the customer added, a bid or an upgrade purchase since the close; ticket purchases, train swaps and app opens do not count (Sheran's reply to Ivi, 29 Sep 2026).
Computed trait, nightlyHas the customer come back and done something real in the auction flow since the close, on the pinned event set, never bare app-opens. Read at day 3.
Used where: The day-3 gate
PHASE 1
ships now
Launch-blocking
Analytics-built; contract on the Analytics brief tab.
One of our best customers
rfm_power_user
LIVE IN BRAZE
Verified live in the Braze CDI, 31 Aug: rfm_segment reaches Braze daily (203,342 rows); the power-user cut is selectable in the sender today.
Computed trait, syncedIn the power band, so the day-3 recovery send is suppressed.
Used where: The day-3 suppression
PHASE 1
ships now
Delivered
Analytics-built; Engineering’s only involvement is closing the Braze segmentation ticket.
Contacted support since the close
support_contact_since_close
DOES NOT EXIST YET
No support-contact signal in the warehouse today.
Computed trait, nightlyRaised a support ticket since this auction closed, resolved tickets included: a customer already talking to us about the episode is not chased at day 3.
Used where: The day-3 suppression
PHASE 1
ships now
Improves the send, does not gate it
Analytics-built; contract on the Analytics brief tab.

Not listed because there is nothing to build: the recovery facts ride the close event in the first row; the parked time-left reword (its message moves to Braze instead); and two pins the campaign writes and reads itself (D8, D9), no engineering or analytics build.

13 changes the customer never sees: what is broken today, the fix we are asking Engineering for, and why it belongs in this journey rather than a general engineering backlog. None is a comm and none needs a decision from Marketing. Each one either loses a message this journey depends on, or decides whether something else can be built at all.

2Eight delivery fixes

Six of the seven are fixes and configuration rather than new events. A wording release on a live native send costs hours and ships now; a new event or feed queues behind the data it needs.

The delivery fixThe fix we are asking forWho does itPhase
The journey ownership flag (journey_state): the shared buildBuild journey_state once, estate-wide: one attribute carrying the owning journey, the episode id and the entry time. Every owning canvas writes it at entry and clears it at exit; every broadcast and wave segment excludes on it; it is mirrored nightly to the warehouse and readable at render by the home surface. The Braze developer wires the canvases and the exclusion segments, Engineering mirrors it to the warehouse and exposes the render read, Analytics keeps the state history. One board row carries it for all ten journeys.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
Every journey's stand-down and hand-off assumes one owner per customer, and the flag those rules read is campaign-written today: invisible to broadcast segments, the warehouse and the app, so the rule cannot be enforced. Two live journeys can reach one customer in one week with clashing messages.
Why it sits in this journey
Here it carries the per-auction recovery episode and the recovery-family cap; every recurring batch audience must exclude it before go-live.
The rule that gates an email on a pushRemove the dependency: send the email in its own right, whether or not the push succeeded.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
The newer auction system will not send an email until the matching push has succeeded first. If the push fails for that customer, the email never sends: it is skipped silently, with no retry. So an email can be lost by a push problem that has nothing to do with email.
Why it sits in this journey
The single largest delivery risk in the estate, and it decides whether anything else can move to Braze: an email that has to arrive every time cannot depend on a push arriving first.
Nothing watches the Braze keyAdd monitoring and an alert on the Braze key fetch, so a failed fetch is seen the moment it happens.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
If the sending service cannot fetch its Braze key, all seven code-triggered Braze sends stop at once and silently. Nothing alarms on it.
Why it sits in this journey
Five of this journey's comms are among those seven. A silent total stop would look exactly like a quiet week.
Native push wording lives only in the push-service repoExport the file onto the shared record, so copy can be reworked without reading the repo.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
The wording of twelve of the twenty-one native push types exists only in one file in the push service, including both time-left pushes and the close notification. Engineering note, 25 Aug: the file sits in a shared repo, so anyone can propose a wording change and put it up for review; what is missing is the wording on the record the copy work reads.
Why it sits in this journey
Those three are the sends this journey rewrites first.
Events built but never arriveInclude the permitted-list entry in every new-event ask, and estimate it as part of the event.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
Sixteen backend events are permitted through to Braze. An event Engineering builds does not reach Braze until it is separately added to that list, and that second step is missing from most estimates.
Why it sits in this journey
Four of this journey's queued items are new events. Each would otherwise be built, delivered, and arrive nowhere.
Five messages nobody knows are aliveAnswer whether any standard auction still runs on the older system; then either remove the five, or bring their wording onto the record like everything else.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
Five push types fire only on the older auction system, and whether any standard auction still runs there could not be settled from code.
Why it sits in this journey
It decides whether five messages in this journey are live comms needing a rewrite or dead weight to delete. Today they are carried as both.
The closed-early push can send twiceDeduplicate so one close produces exactly one send.BACKEND ENGPHASE 1
ships now
▸ The problem today, and why it sits here
The problem today
The older system fires it through two independent routes from one transition with no dedupe. A buy-now sell-out on a newer-system auction can produce those two plus the newer system's own.
Why it sits in this journey
It fires at the moment a customer is being told something went wrong, which is the worst moment in the journey to send the same thing three times.
Nobody is told their payment window expiredBuild the expiry event, and a message that goes with it.BACKEND ENGPHASE 2
gated
▸ The problem today, and why it sits here
The problem today
When a payment retry window expires on the newer system the bid is marked failed and nothing is sent. The customer finds out by opening the app. The older system has a message for this and is being retired underneath it.
Why it sits in this journey
A customer loses an upgrade they had won and hears nothing. It is a live gap that widens as auctions migrate.
3Five defects in the app itself

Front-end defects, all fixable now. Each one loses or misdirects a message this journey sends.

The defectThe fix we are asking for
The winner push lands on the home screenPoint the tap at the upgrade they just won.
▸ The problem today, and why it matters here
The problem today
On Android, the tap on the auction-won push goes to the home screen rather than the upgrade they just won.
Why it matters here
The best moment in the journey, thrown away by the tap.
The payment push is silent on iPhone and names the wrong screenMatch the push copy to the auction page the tap opens. Add a sound to the push payload; Marketing supplies the short list of which push types should be audible.
▸ The problem today, and why it matters here
The problem today
The copy sends the customer to a payment screen while the tap opens the auction page, which carries the recovery action for each failure type (try again, or add a new card). No native push requests a sound on iPhone today, so all arrive silently; this ten-minute message is where that costs most.
Why it matters here
The customer has ten minutes to act: the weakest link in the journey.
The lock-screen card can stallFix the token upload where permission exists, measure how often the card is un-updatable, and design the starting state to stand on its own.
▸ The problem today, and why it matters here
The problem today
When the customer has not granted the update permission, the per-activity token is never uploaded and the card cannot be updated at all: it sits at its starting state until the system times it out. Engineering note, 25 Aug: that half is platform behaviour, not a fixable bug; we can launch the card, we cannot force it to update.
Why it matters here
This journey proposes building the in-auction status on that surface.
A dead check on the closed-early pushDecide the intended behaviour, then implement it.
▸ The problem today, and why it matters here
The problem today
On Android the app tries to suppress this notification while the app is open, but that check has been dead code since September 2024. Nearly everyone gets it in every state, and a small group silently gets none.
Why it matters here
Neither outcome is intended; it is what the code does.
The outbid pop-up misfiresOnly when the current bid is above the customer’s maximum.
▸ The problem today, and why it matters here
The problem today
Shown to a customer whose maximum was above the current bid.
Why it matters here
The winner is told they are losing.

Source: the code-sent communications audit and the Braze console pull, both 12 August 2026. Working draft, not signed, and not yet on the sprint board.

Working detailPack version: 15 Oct 2026, 10:39 (London)