Black Friday in Kenya is not a Friday; it is the whole of November. Jumia's 2025 campaign ran October 31 to November 30; the group moved 5.1 million orders across October–November, 3.2 million of them during the Black Friday event itself — 61% from outside the main cities, which is exactly where a delivery gets coordinated by phone call. This playbook covers the three calls that protect a cash-on-delivery operation through the peak: the checkout confirmation call that kills fake orders before dispatch, the delivery-day coordination call with a press-1/press-2 keypad prompt, and the failed-delivery rescue call — plus the pacing, retry, and cost math to run all three for 10,000 orders.
The dates first, framed on the record rather than on hope. Jumia's Black Friday 2025 ran October 31 through November 30 with discounts up to 70%, per its Nairobi launch coverage (as of 2025-10-31). Carrefour and Naivas ran parallel "Black November" campaigns the same month. Campaign dates for 2026 had not been announced when this guide was prepared, but the month-long pattern has held for years, and Black Friday proper falls on November 27, 2026. If the pattern holds, campaigns are already live as you read this.
The volume is real and it skews away from Nairobi. Per Jumia's preliminary KPI release for October–November 2025 (published December 2025): 5.1 million orders group-wide, up 30% year on year, with the Black Friday event itself driving 3.2 million of them — and 61% of orders came from outside capital and main urban centres. For Kenya specifically, Business Daily reported (early 2026) that Jumia Kenya's Q4 2025 sales grew 48% year on year to KES 5.4 billion. The festive quarter is the growth engine, and the hangover is visible in regulator data: Communications Authority sector statistics show domestic parcel volumes fell 25.5% quarter-on-quarter to 2.76 million in January–March 2025, straight off the festive-quarter peak (CA Sector Statistics, published 2025).
Now connect the two numbers. If most orders come from outside the main cities, most deliveries end in Kenya's upcountry last mile — a parcel rides a shuttle or courier to the destination town, lands at a booking office or pickup station, and someone phones the recipient: your parcel is at the office, come collect. The last mile of logistics is already digital; the last mile of communication is a clerk or a rider with a call list and paid airtime. Cash on delivery makes it worse: an unreachable customer is not a re-delivery, it is a failed sale plus a wasted rider trip.
COD checkout has no payment gate, which is the point — and the problem. Nothing stops a mistyped phone number, a second thought, or an outright fake order from entering your dispatch queue. Every one of those you ship costs a real rider trip, and during Black November your riders have no trips to waste.
The fix is a confirmation call fired from your order-created webhook, within minutes of checkout, while intent is fresh: a 20–25 second script that reads back the order and total in KES, then asks for a keypress to confirm. Orders that are not confirmed by call (or a follow-up attempt) never reach dispatch.
npm install @sautikit/node@0.2.0
import { SautikitClient } from "@sautikit/node";const client = new SautikitClient({ apiKey: process.env.SAUTIKIT_API_KEY });// Fired from your order-created webhook, within minutes of checkout.const { call_id, status } = await client.calls.create({ from: SHOP_NUMBER, to: [order.msisdn], idempotencyKey: `bf-confirm:${order.id}`,});// status: "ringing" — outcomes arrive on your events_url.
@sautikit/node is in preview — pin the version; the API may change before 1.0.
The idempotency key matters more in November than any other month. Keyed on the order ID, a crashed worker or a replayed queue message can retry the create call as often as it likes — the SDK sends it as the Idempotency-Key header, the platform dedupes the request, and your customer is never double-dialed. See idempotency on the calls API for the semantics.
When the customer answers, the platform POSTs the call state to the voice_callback_url configured on your number's routing, and your handler returns the confirmation script as JSON voice actions. The handler is the same shape as the delivery-day one below — a say, a getDigits, and a dispatch on the collected digit.
This is the call that moves the failed-delivery number. On the morning a rider or shuttle is dispatched, every order on the manifest gets a short call: press 1 if you'll be at this location today, press 2 to reschedule. Riders then run a manifest of confirmed-reachable customers instead of gambling airtime on silence.
One handler serves the whole flow. getDigits collects the keypress, and — this is the part people get wrong — it has no follow-up URL. When digits are collected, the platform POSTs back to the same voice_callback_url with the Digits field populated, and your handler branches on it:
import express from "express";import { sauti } from "@sautikit/node";const app = express();app.use(express.json()); // the voice callback delivers JSONapp.post("/voice/delivery", async (req, res) => { const digits = req.body.Digits || ""; const msisdn = req.body.clientDialedNumber; // the customer we dialed (destinationNumber is your own DID) const order = await ordersByPhone(msisdn); if (!digits) { // First hit (call answered): ask the question, collect one digit. return res.json( sauti .say( `Habari, hii ni ${SHOP_NAME}. Your order number ${order.shortRef} ` + `arrives today at ${order.deliveryArea}. ` + `Press 1 if you will be at this location today. ` + `Bonyeza mbili kubadilisha siku — press 2 to reschedule.`, { language: "en-KE" }, ) .getDigits({ numDigits: 1, timeout: 8 }) // timeout is seconds .say("Hatukupokea jibu. We will try again later. Kwaheri.", { language: "en-KE" }) .hangup(), ); } if (digits === "1") { await markReachableToday(order.id); return res.json( sauti .say("Asante. The rider will call when they are close. Kwaheri.", { language: "en-KE" }) .hangup(), ); } if (digits === "2") { await queueReschedule(order.id); // drop off today's manifest, offer new slots return res.json( sauti .say("Sawa. Our team will send you new delivery options shortly. Kwaheri.", { language: "en-KE" }) .hangup(), ); } // Unrecognised input: replay the question. return res.json(sauti.redirect("https://shop.example.com/voice/delivery", { method: "POST" }));});
Two operational notes. First, routing lives on the number, not the call — set it once with PUT /v1/numbers/{id}/routing. If one number runs both confirmation and delivery calls, keep a single handler and let the order's state decide which script plays. Second, keep DTMF prompts short and the digit space small; keypresses on Kenyan mobile networks are lossy enough that a one-digit binary question dramatically outperforms a menu (see reliable DTMF detection).
Some deliveries fail anyway: phone off at dispatch time, mteja hapatikani, a customer who pressed 1 and then went to a funeral. In the standard setup this parcel bounces back to the warehouse or sits at a pickup station accruing storage days until it is returned — a lost COD sale.
The rescue call automates the recovery window. When the rider marks an attempt failed, queue an automated call from the same branded number that confirmed the order. This detail carries the flow: a customer who will not answer a rider's unknown personal number will answer the line that has called them twice already about this order. The script offers the two useful exits — press 1 to retry delivery tomorrow, press 2 to collect from the nearest pickup station — using exactly the handler pattern above.
Spread rescue attempts across windows rather than hammering: 15 minutes after the failed attempt, again two hours later, once the next morning, then stop. Sautikit bills per second after connect (per the pricing page, as of 2026-06-30), so an attempt that rings unanswered adds nothing to your talk-time spend — the retries cost patience, not money.
A 10,000-order November is not a steady drip. It is a baseline with cliffs on flash-sale days and a hard spike around November 27. Plan four things.
Pace the queue. Confirmation calls are event-driven and naturally smooth. Delivery-day calls are a batch: 500 orders on one morning manifest, dispatched between 08:30 and 11:00, is 500 calls over 150 minutes — a little over 3 call starts per minute. Feed your dialer from a queue with a concurrency cap rather than a for-loop, and it stays boring even on flash days.
Define retry windows before the month starts.
Call
On no answer
Stop after
Checkout confirmation
+15 min, +2 h
3 attempts, then hold order for manual review
Delivery-day coordination
+30 min (before rider departs)
2 attempts, then rider skips or calls direct
Failed-delivery rescue
+15 min, +2 h, next morning
4 attempts, then route parcel to pickup station
Keep all attempts inside daytime hours — roughly 8am to 8pm. That is courtesy and campaign hygiene rather than a statutory requirement, and it protects your answer rates for the rest of the month.
Pre-fund the wallet. Sautikit is prepaid: at zero balance, POST /v1/calls returns 402 wallet.insufficient_balance and your confirmation pipeline silently stalls — the worst possible failure mode mid-flash-sale. Set a low-balance threshold with an alert, and top up over M-Pesa STK push before the big days rather than during them.
Watch outcomes, not dispatches. Point the number's events_url at a handler that records call.completed outcomes per order, and dedupe on the event ID. Mid-campaign, you can also pull call stats and wallet balance straight from Claude through the Sautikit MCP server — a daily ops standup without opening a dashboard.
Sautikit pricing (as of 2026-06-30): KES 3/min outbound — KES 0.05 per second, billed per second after connect. See /pricing for current rates. Assume one order in ten dies at confirmation (fake, mistyped, changed mind), so 9,000 orders reach delivery day, and assume 1,200 rescue calls across the month including retries that connect.
Call
Answered calls
Avg length
Total seconds
Cost at KES 0.05/s
Checkout confirmation
10,000
25 s
250,000
KES 12,500
Delivery-day coordination
9,000
30 s
270,000
KES 13,500
Failed-delivery rescue
1,200
35 s
42,000
KES 2,100
Total
562,000
KES 28,100
The arithmetic per call: 25 seconds × KES 0.05 = KES 1.25 per answered confirmation; the whole three-call program lands at KES 28,100 ÷ 10,000 ≈ KES 2.81 per order. Set that against what it buys. The 1,000 fake orders killed at confirmation are 1,000 rider trips never dispatched. And if the coordination and rescue calls convert even 2% of orders — 200 sales — that would otherwise have failed on reachability, then at an assumed average basket of KES 2,500 that is KES 500,000 of rescued COD revenue against KES 28,100 of call spend. The assumptions are yours to tune; the shape of the trade rarely changes.
November's peak does not end, it hands over. December brings the travel exodus, matatu and bus fares that roughly triple (per Nairobi press reporting, December 2025), and parcels that increasingly move through shuttle desks and pickup stations to recipients who are themselves on the road. The three-call pattern carries straight over — arrival notifications replace confirmation calls, and the rescue flow works even harder when recipients are travelling. If you are wiring this up for Black November, you are also building your December stack; the broader delivery patterns are in cut failed deliveries in Kenya. And when the campaign needs SMS receipts or WhatsApp alongside the calls, that is Helloduty, the multi-channel CX platform Sautikit is part of.