Highlights
- One Flutter codebase serves customers, drivers and admins on Android and the web
- Every shipment transition is atomic, audited and enforced by Postgres, not the client
- Real-time admin dashboard and push notifications to drivers and customers
- Photo proof of delivery and battery-aware GPS tracking while in transit
- 22 engineering documents and 6 architecture decision records written before the code
The problem
The company dispatched deliveries over phone calls and paper waybills. There was no proof of delivery and no audit trail, customers couldn't see where their parcels were, and management had no data to run the business on.
The brief was a digital chain of custody: every shipment, from booking to delivery, recorded and visible to the right people.
What it does
- Customers book shipments with multiple packages and photos, accept or decline the price offer, track progress on a timeline, and rate deliveries.
- Drivers receive assignments, accept them, step through pickup and delivery, report GPS location while in transit, and capture photo proof of delivery.
- Operations staff review and approve bookings, send price offers, assign or reassign drivers, manage the driver fleet, and watch a live dashboard with charts and metrics.
- Anyone can track a shipment publicly by its tracking number, without logging in.
- Notifications reach phones through push notifications and an in-app notification centre.
Architecture
- Clients
- Flutter Android app (customer & driver)
- Flutter web (admin & public tracking)
- Edge
- Auth with custom role-claims hook
- 12 Deno edge functions
- Database
- PostgreSQL state machines (53 functions)
- Row-level security
- Audit log & event history
- Services
- Realtime
- Object storage (media, POD, APKs)
- Push notifications
- Cloudflare Pages
Engineering challenges
A shipment state machine nobody can bypass
Shipments move through 16 states (draft, pending approval, price offer, assigned, in transit, delivered, returned and more) with explicit forbidden transitions. Rather than trusting the app, the rules live in PostgreSQL functions and triggers. Each transition is atomic and writes an immutable event and an audit row. New states were later added with purely additive migrations.
Identity and onboarding edge cases
A custom auth hook adds role claims to every login token and refuses login when no profile exists. That turned out to fire during email-confirmation sign-up too, which would have locked out every new customer. The fix, documented in an architecture decision record, was an explicit "unregistered" state with a complete-profile flow, followed by invite-based onboarding for drivers and staff.
Users stuck on old versions of the web app
Flutter web always ships the same file name, and the CDN was caching it for hours, so users kept seeing old builds after a release. Per-file cache headers were being silently ignored. We confirmed the cause from the CDN's cache-status headers and fixed it with a site-wide revalidation rule that still allows cheap 304 responses.
Battery-aware, spoof-proof GPS
Driver location reports are scheduled one after another so they never overlap, run only while a shipment is in transit, and stop when the app leaves the foreground. The server works out which driver is reporting from the login token, so a client can't report a location on someone else's behalf.
Results
Phase 1 went from first commit to production in about two weeks and is live, with the Android app distributed directly to drivers and customers. The documentation-first approach (PRD, domain model, state machines, sequence diagrams and test plans before code) is what made that pace possible without cutting corners.