Grabbit Express

A menu hub and a 24/7 WhatsApp dispatch engine for an electric delivery fleet.

Grabbit Express landing pageSite
Grabbit menu directoryMenu
Grabbit dispatch consoleDispatch

Context

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:

WebhookHMAC-SHA256 verification with a 300-second replay window
Session lockDistributed per-phone lock with stale expiry, so two messages cannot race
Idempotency120-second cooldown on repeated requests
Booking indexCompare-and-swap writes with an in-process mutex
Dead-man's switchPauses 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.

Grabbit dispatch system map: WhatsApp ingress, HMAC verification, session lock, decision layer, state and notifications
The system boundary: WhatsApp ingress, HMAC verification, a per-phone session lock, the pure decision layer, Postgres state and outbound notifications.

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.

Grabbit menu directory with merchant cards and category filters
The menu directory: every merchant, grouped by category, with the service areas beside it.

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.

Grabbit dispatch console with fleet status, fares, bookings and rider bar
The dispatch console: fleet state, today's fares, active bookings and the quick-ping bar. Rider names redacted.

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.

A Grabbit booking handled on WhatsApp, with the customer greeting redacted
A booking handled end to end on WhatsApp. The greeting is redacted; the contact number is blurred.

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

268automated tests across 91 suites, 0 failuresNode test runner · 20 October 2026. Covers the decision layer, guards, parser, time handling and webhook.
80%faster replies to customer messagesMeasured over roughly three months against the manual queue. Internal operations measurement; not reproducible from the repository.
1,796messages handled by the agent, with 6 takeovers waitingA console snapshot from a real shift.

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.
NextBlocknauts LMS