A delivery exception handler on AWS for a few dollars a month
A customer emails on Thursday asking where their order is. Tracking says “in transit”, which it has said since Saturday. Nobody looked, because nothing flagged it: there is no exception status, no failed delivery, no problem at all according to the carrier. The parcel has been sitting behind a roller door in a depot for five days. This post walks through a small system that notices on Tuesday.

Key takeaways
- Silence is the signal. No scan for several days beats any exception code.
- Carrier statuses are unreliable in known directions and need translating.
- Triage matters: most exceptions resolve themselves and a few need action today.
- Never give a customer a delivery date you do not have.
- Designed on AWS for about $2 a month.
The whole system on one page
Before any code, here is the shape of what we are designing.
- App integration
- Analytics
- Front-end & mobile
- People
Why carrier statuses are not enough
Every carrier provides exception codes and they are genuinely useful for the cases they cover: address not found, refused, damaged in transit. The problem is what they do not cover, and the specific ways they mislead.
“Attempted delivery, nobody home” is sometimes exactly that and is sometimes a driver who ran out of hours and needed to close the round. A parcel mis-sorted to the wrong depot generates no exception at all; it simply stops moving. And a shipment that was never actually collected shows as despatched forever, because the first scan never happened.
What runs (the inside)
- The watcher. Ingests tracking events and, more importantly, notices when they stop. Part 2.
- Triage. Sorts exceptions into the ones that fix themselves and the ones that need somebody today. Part 3.
- Contact. Tells the customer what is known, in the cases where telling them helps. Part 4.
One exception, end to end
- App integration
- Management
- Analytics
In plain words
An order goes out on Friday. It is scanned into the network on Saturday morning and then nothing happens. Sunday is not a working day, Monday passes with no scan, Tuesday passes with no scan.
On Tuesday afternoon the watcher flags it: three working days since the last event, against a service where the typical gap between scans is under a day. That threshold is not a guess — it comes from the carrier’s own observed behaviour on that service, which the system has been measuring.
Somebody raises a trace with the carrier on Tuesday and emails the customer: your parcel has not moved since Saturday, we have asked the carrier to find it, we will tell you tomorrow what they say. No new delivery date, because there is not one. That message is the entire product, and it lands two days before the customer would have written in annoyed.
Design rules that shaped every decision
- Watch for silence, not just for exception codes.
- Thresholds come from each carrier and service’s observed behaviour, not from a guess.
- Triage before contact. Most exceptions resolve themselves within a day.
- Never state a delivery date that is not a fact.
- One message per exception, and a follow-up only when something actually changed.
- Record which exceptions resolved on their own, so the thresholds can be tuned.
Why this shape
The failure mode this replaces is not that nobody cares about late deliveries; it is that nothing surfaces them until a customer does. Support then spends its time reacting to people who are already annoyed, on parcels that have been stuck for days, with no information beyond the tracking page the customer has already read.
Shifting that by two days changes the interaction completely. The same problem, communicated first and honestly, generates a fraction of the support load and a different customer reaction, without any improvement in the actual delivery performance.
The next four posts walk through each piece: how an exception is detected, what each kind actually needs, how the customer gets told, and what the exceptions reveal in aggregate. One diagram per post, a cost breakdown, and an engineering reference at the end.
All posts