DispatchCheck — Devpost Write-Up
Inspiration
Cash-on-delivery e-commerce is huge across Nigeria and Ghana — most small sellers run entirely through WhatsApp or Instagram, with no formal checkout. The problem I kept coming back to: a seller dispatches a rider, and only then finds out the customer is unreachable, changed their mind, or gave a bad address. The seller pays for that wasted trip every time, and there was no automated way to catch it before the rider leaves. I wanted to fix that using CALL-E, and make sure it worked the same way whether the seller is in Lagos, Accra, or anywhere else COD e-commerce runs.
What it does
DispatchCheck calls the customer before a rider is dispatched. It confirms three things: they still want the order, the delivery address is correct, and they'll be available today. The call can run in Nigerian Pidgin, English, Yoruba, Igbo, Hausa, or Twi. The result comes back as a structured status — confirmed, declined, address mismatch, or unreachable — and only a confirmed order can actually be marked dispatched; that rule is enforced by the backend, not just the UI. A dashboard shows every order's status live, plus a running count of calls placed, orders confirmed, and roughly how much wasted-delivery cost was avoided.
How we built it
The backend is NestJS, structured around two modules: OrdersModule
(order lifecycle and the dispatch state machine) and
CallVerificationModule (the CALL-E integration). Each order gets a
structured resultSchema sent to CALL-E, so the agent's phone
conversation comes back as clean JSON — confirmed / declined /
address_mismatch — instead of something we'd have to parse out of a
transcript. The dashboard is a single static page styled like a dispatch
control board: dark theme, monospace order data, live status pills, and a
stats bar tying the confirmation flow directly to the money it saves.
Challenges we ran into
The biggest one: the installed @call-e/calle npm package's TypeScript
types didn't match the live API. The types declared a recipient object
and a policy object as required fields — but the real API rejected both
outright with "Extra inputs are not permitted." I had to go straight to
CALL-E's own OpenAPI docs to find the real contract: no recipient
object at all — the phone number goes directly in the task text, which
the agent infers automatically.
Second: @call-e/calle ships as an ESM-only package, while a standard
NestJS project compiles to CommonJS. A plain import() gets silently
downleveled by tsc into a require() call, which throws
ERR_REQUIRE_ESM at runtime for an ESM-only dependency. The fix was
routing the dynamic import through new Function(...) so it's invisible
to TypeScript's transpiler and survives as a real import at runtime.
Third, and the trickiest to catch: a call could genuinely succeed — phone rings, real conversation happens — and still get reported as failed, because a transient network error during the result-polling step threw away the whole result. The fix was splitting call creation and result-polling into two separate steps with retries on the polling side, so a dropped connection retries against the same call instead of losing track of a call that actually worked.
Accomplishments that we're proud of
Getting a genuinely real, end-to-end phone call working — not a mock, an actual call to a real number, with a real conversation, resolving to a structured, trustworthy outcome. And building in a fail-safe default: DispatchCheck never marks an order dispatchable unless it was explicitly confirmed, which matters a lot more than a demo flourish once you imagine this running against real orders and real money.
What we learned
An SDK's published types aren't a substitute for testing against the real, live API — the two can drift, and the only way to catch it is to actually place a call and read the real error response. I also learned how much of "supporting Africa" is really about defaults: Nigerian Pidgin as the default call language, not an afterthought option, changes how natural the whole interaction feels for an average customer.
What's next
Multi-tenant API keys so multiple sellers can use one deployment safely, a real webhook receiver for Shopify/WhatsApp Business checkouts instead of a manual order form, and an SMS fallback for when a call goes unreachable so the seller still gets a shot at confirming the order another way.
Built With
- call-e
- css3
- html5
- javascript
- nestjs
- typescript

Log in or sign up for Devpost to join the conversation.