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

Share this project:

Updates

Submission history