Retail & e-commerce
Cutting failed deliveries by more than half by fixing the address, not the map
An order state machine, delivery slots tied to real rider capacity, and a customer app and a rider app built for the phones and networks people actually have, not the ones a demo runs on.
- On-time delivery rate
- low seventies → low nineties, percent
- Failed first-attempt deliveries
- cut by more than half
- Repeat purchase share via the app
- roughly tripled
- Sector
- D2C retail & quick commerce
- Estate
- ~40 dark stores/hubs across 3 metros
- Engagement
- Product build, then a retained pod
Stack
- Flutter
- OSRM
- Kafka
- PostgreSQL
Practices involved
Discuss a similar problemThe situation
The retailer's stack had grown by accretion: a storefront plugin, order tracking in a shared spreadsheet once a marketplace integration fell over, and delivery handled by whichever local courier picked up the phone that day. There was no owned customer app, so no push notification, no saved address, and no channel for a second order that did not go back through a marketplace taking a cut of it.
The constraint
Two things made a straightforward rebuild the wrong plan. First, a delivery promise like "in 90 minutes" has to reflect the rider capacity and stock actually available from the nearest hub at that moment, not a flat radius on a map — overpromising a slot was the single biggest driver of bad reviews before we started. Second, addresses in the markets served are frequently informal — a landmark and a floor number, not a reliable postal code — and a failed delivery costs a return trip, a frustrated rider and a customer who does not order again.
What we built
Order orchestration as the source of truth
One order state machine spans cart, payment, pack, dispatch, delivery and returns, replacing three disconnected systems. Every state change is an event; the customer app, the rider app, the hub dashboard and finance subscribe to it rather than polling three different sources that used to disagree.
Slot promising tied to real capacity
Delivery slot availability is computed from live rider capacity, hub inventory and a real drive-time matrix, recomputed as conditions change, rather than a static radius set once at launch.
Address resolution that assumes the address is wrong
Customers drop a map pin and add a landmark note; the rider app shows both, and a reusable "last 200 metres" note attaches to the saved address for next time. Most of the reduction in failed first attempts came from this, not from a better map provider.
A rider app for the phones people actually carry
Offline-first, with a local queue of assigned stops and opportunistic sync, running acceptably on low-end Android devices, because a meaningful share of the delivery fleet uses hand-me-down phones on patchy data. Cash-on-delivery collections log against the order automatically and reconcile at day close instead of on a whiteboard.
A customer app built around the second order, not the first
Push-driven reorder flows, live tracking with a recalculated ETA instead of a static countdown, and saved addresses that get more accurate every time they are used.
What changed
On-time delivery moved from the low seventies into the low nineties, percentage-wise, and failed first attempts fell by more than half, mostly from the address work rather than routing changes. The app now carries a meaningful share of orders as repeat purchases, which is revenue the retailer keeps instead of paying a marketplace for it a second time.
What we would do differently
The first rider app version polled GPS location every five seconds to keep tracking smooth. It drained batteries within a shift, and riders started quietly turning the app off mid-route, which silently broke tracking for exactly the deliveries where it mattered most. We moved to adaptive, event-driven location pings in month four. Battery and data budget should have been a stated constraint from day one, not something we learned from riders switching the app off.
Outcomes
- On-time delivery rate
- low seventies → low nineties, percent
- Failed first-attempt deliveries
- cut by more than half
- Repeat purchase share via the app
- roughly tripled
Client identity withheld under a mutual NDA. Figures are illustrative — rounded and directional, meant to show the shape of the change rather than an audited result. We will walk through the real numbers, how they were measured, and put you in touch with a reference, under NDA on a call.
More work
Other engagements.
Case studiesCutting stockouts across 240 stores with a forecast the buyers trust
A hierarchical demand forecast, a promo-aware feature store and a replenishment workflow the category team can override — because a model nobody overrides is a model nobody uses.
Read the case studyEnergy analytics across 14 buildings and four BMS vendors
One data plane over BACnet, Modbus and a proprietary head-end, with fault detection that tells a facilities manager which asset to look at before the complaint arrives.
Read the case studyTurning 40,000 support calls a month into a product backlog
Diarised transcription, an LLM extraction pipeline with a graded evaluation set, and a review console where quality leads correct the model in place.
Read the case studyNext step
Tell us what you're trying to ship.
Send the brief, the RFP, or three messy sentences about the problem. You get a written point of view from an architect within two working days — not a sales deck.