Grabbit Express
A menu hub and a 24/7 WhatsApp dispatch engine for an electric delivery fleet.
Site
Menu
DispatchContext
- Role
- Builder — product, design, front end, back end, operations
- Team
- Solo build, with the operator and three riders
- Duration
- 2026
- Stack
- Next.js 16, TypeScript, DeepSeek, Supabase Postgres, WhatsApp API, Vercel
- Status
- Live; production repo private
Grabbit Express is an electric delivery and ride-hailing service in Pamplemousses, Mauritius. It began as a manual operation: a phone, WhatsApp, and a person answering every message. Someone read each request, found the menu or called the merchant, worked out which rider was free, and replied. That worked at low volume. As the service grew, the queue outgrew the person answering it.
The fix came in two parts. The public site became a hub: menus from every merchant Grabbit delivers from, the categories, the nine areas covered, and a single path into WhatsApp. The dispatch engine became the responder: it receives the message, works out what the customer wants, checks whether it can be done, and routes it to one of three riders. When the request does not fit a rule, a human takes over.
The manual queue worked until it didn't.
The manual version worked until the volume arrived. Every order needed a person to read the message, find the right menu, check a rider, and reply. Menus lived in different places: a PDF here, a photo there, a restaurant's own page somewhere else. Customers asked the same questions all day. When two orders arrived at once, one waited. The operation could not add riders without adding someone to answer the phone.
Small fleet, physical rules
Three riders, one district, electric bikes with limited range. A booking is not a database row; it is a promise about time, capacity and distance. Two customers cannot hold the same rider in the same slot, and a rider needs a buffer between jobs. Letting a language model decide these things would mean trusting a probabilistic system with a physical constraint.
A booking is not a database row; it is a promise about time, capacity and distance.
Failure is expensive
A wrong answer is not a bad paragraph. It is a customer told a rider is coming when none is available, or a receipt parsed into the wrong amount. The system had to assume the model would time out, return malformed JSON, or choose the wrong tool.
Private by default
The production repo is private and contains customer phone numbers. The work has to be proved without it: tests, a sanitized showcase, the public site and the console.
Approach
The site is the catalogue; the agent is the responder
The manual operation kept menus in a folder of PDFs, screenshots and links. The replacement is one page that holds every merchant Grabbit delivers from, grouped by category, with the service areas and delivery promise beside it. Every path ends in the same WhatsApp link, prefilled with the district, so the agent knows where the customer is before the first reply.
The model proposes; code decides
Booking rules, capacity checks, rider assignment and the guards around them are pure functions. They take state and a request, and return a decision. They do not call the model, the database or the network. Every rule can be tested without an LLM, a WhatsApp connection or a database. A production incident can be replayed as a scenario.
Reliability in code, not in the prompt
Prompts cannot enforce a booking window. These live in code:
| Webhook | HMAC-SHA256 verification with a 300-second replay window |
|---|---|
| Session lock | Distributed per-phone lock with stale expiry, so two messages cannot race |
| Idempotency | 120-second cooldown on repeated requests |
| Booking index | Compare-and-swap writes with an in-process mutex |
| Dead-man's switch | Pauses the agent after three consecutive failures |
A receipt parser that costs nothing
The parser inflates PDFs with Node's native zlib, extracts the text operators and reads the fields with keyword gates. Images and unrecognised layouts fall back to a vision model. The common case stays cheap.
Shipped
The public site
A hub with every merchant menu in one place, four ways to order, the catalogue, nine service areas, delivery time, payment in MUR and an FAQ. It replaced the folder of PDFs and screenshots the manual operation worked from. Every order path ends in WhatsApp: no account, no checkout, no app install.

The dispatch console
A single operational view: whether the fleet is active, today's fares, active bookings, shift status, how many messages the agent has handled, how many need a human, and a one-click quick-ping bar for each rider on shift.

The WhatsApp flow
A customer can book a ride or a delivery, give a pickup and drop-off, get a summary with the fare and time, and receive the rider's details. Reschedules and receipt intake run through the same thread.

The product deliberately does not have a customer app or a checkout on the site. The operation runs on WhatsApp because that is where the customers already are.
Evidence
19 scenarios, 27 turns in the evaluation harness. It replays production incidents with zero LLM calls and zero network calls.
24,850+ km, 3,240+ kg, 148+ deliveries. Public service metrics on the live site.
Reflection
The model was never the hard part. The hard part was making its failures uneventful, and making the queue stop depending on one person's attention.
The first version of Grabbit needed a person for every message. The final version needs a person for the messages that do not fit a rule. That is the distinction I care about: automate the queue, not the judgment.
Separating decisions from I/O changed how I write software. A rule that cannot be tested without a model is a rule I do not trust. The test suite became the specification, and the evaluation harness became the place where production incidents go to become permanent checks.
Automate the queue, not the judgment.