Courier tracking and delivery
Merchants send parcels. Each parcel goes from pickup to hub to a delivery rider to the customer. Merchants and customers track it, and delivery needs proof.
In plain words
A Facebook shop in Mirpur sends you a parcel. A rider picks it up and scans it. At the hub it's scanned again in, then out, then onto a delivery rider's bike. Each scan adds a line to the parcel's history, which is what you see when you open the tracking link.
The delivery rider's phone sends its location every few seconds. On a weak network, the 2:03 ping arrives after the 2:05 one, and the 2:05 one arrives twice. The map keeps the newest one as the moving dot, puts the late one in the trail where it belongs, and ignores the copy. At your door, the rider asks for the 4 digit code from your SMS. Wrong code, no delivery.
Examples
- Courier companies like DHL and FedEx work from scan events at each hand off.
- Phones batch GPS pings and send them every 30 s to save battery, more often near the customer.
- The delivery code is stored only as a hash, so staff can't read it from the database.
- Failed attempts are events too: not home, reschedule, and after 3 tries, return to the merchant.
1. Requirements
Functional
- Merchants book parcels and get a tracking code.
- Each hand off is scanned: pickup, hub in, hub out, out for delivery.
- Customers track the parcel and the rider live.
- Delivery needs the customer's one time code.
Non functional
- Tracking history is complete and in order.
- Late and duplicate GPS pings don't confuse the map.
- Works offline on the rider's phone and syncs later.
- Easy on the phone's battery and data.
Questions to ask
- How many parcels a day? How many hubs?
- Cash on delivery? Returns?
- How live is live: every few seconds, or each scan?
- What counts as proof of delivery: OTP, photo, signature?
2. Estimates
| parcels | 300k a day, about 4 per second, mostly created in the evening by merchants |
| scans | about 6 scan events per parcel: under 2M a day |
| rider pings | 10k riders, one GPS ping every 10 s: about 1k per second |
3. API
POST /parcels { from, to, cod } { parcelId, trackingCode }
POST /scans { parcelId, hubId, kind } 204 (pickup, arrive hub, leave hub, out for delivery)
POST /riders/me/pings [{ t, lat, lng }] 204 (batched from the phone)
POST /parcels/{id}/deliver { otp, photo? } 200, or 403 wrong code
GET /track/{trackingCode} status, history, rider position if out for delivery
4. Data model
parcels(id, tracking_code, merchant_id, status, cod_amount, otp_hash) parcel_events(parcel_id, kind, hub_id, at) append only: the tracking history rider positions: Redis, newest per rider rider trails: object storage per day, for disputes
5. High level design
Follow one request at a time, top to bottom.
Scan a parcel
- Hub or rider appPOST /scans with parcel, hub and kind
- Parcel servicechecks the scan fits the route
- parcel_eventsappends the event: the tracking history
- parcelsstatus updated from the event
- NotificationsSMS at key steps
Live position
- Rider phonebatched GPS pings with phone time
- Tracking servicenewest per rider wins, late ones go to the trail, duplicates dropped
- Redislive dot per rider
- Tracking pageshows the dot while out for delivery
Deliver
- Customerreads out the SMS code
- Rider appPOST /parcels/1/deliver with the code and a photo
- Parcel servicecompares against the stored hash
- parcel_eventsdelivered, with time and position
6. Deep dives
What is the source of truth for status?
The scan events. Each scan appends an event, and the parcel's status is updated from it. The tracking page reads the event list, so customers see the full history.
Pings arrive late and twice. What then?
Each ping carries the phone's timestamp. Keep the newest per rider as the live dot; ignore older ones for the dot but keep them in the trail; drop exact duplicates.
How do you prove delivery?
The customer gets a one time code by SMS. The rider must enter it; only a hash is stored. Add a photo and the GPS position at that moment for disputes.
How does the phone save battery and data?
Batch pings and send every 30 s, send more often only when the rider is close to the customer, and keep sending offline pings once the network is back.
See the core idea run
Core logic (JavaScript, tested)
// Phones send GPS pings out of order and twice. Keep only the newest per courier,
// and keep the trail for the map without duplicates.
class Tracker {
constructor() {
this.last = new Map(); // courier to newest ping
this.trail = new Map(); // courier to pings in time order
}
ping(courier, p) {
const seen = this.last.get(courier);
const trail = this.trail.get(courier) ?? [];
if (trail.some(x => x.t === p.t)) return "duplicate";
trail.push(p);
trail.sort((a, b) => a.t - b.t);
this.trail.set(courier, trail);
if (seen && seen.t > p.t) return "late"; // stored for the trail, not the live dot
this.last.set(courier, p);
return "live";
}
where(courier) {
return this.last.get(courier) ?? null;
}
}
// Proof of delivery: the parcel is only "delivered" with the right one time code from the customer.
const confirmDelivery = (parcel, code) => (parcel.status === "out_for_delivery" && code === parcel.otp ? ((parcel.status = "delivered"), true) : false);
7. Failures
| Rider's phone is offline | Pings and scans are queued on the phone with their times and sent later; order by the phone's time, not arrival. |
| Wrong hub scan | Scans are checked against the expected route; a surprise scan raises an alert instead of silently changing status. |
| Customer not home | A failed attempt event, a reschedule, and after 3 tries a return to merchant flow. |
8. Trade-offs
- Status from scans is reliable but only as fresh as the last scan; live GPS is fresh but noisy.
- OTP is strong proof but adds friction; photo only is easier but weaker in disputes.