A utility can phone every customer on an affected feeder before a planned outage with Sautikit for about KES 1.50 per customer reached. A 30-second notice costs KES 1.50 when it is answered and nothing when it is not, and each call leaves a record you can show a regulator or a customer.
Planned-interruption notices in Kenya are usually published as schedules on a website, posts on social media and newspaper notices. All three assume the customer goes looking. A phone call reaches the customer who is offline, who does not read English comfortably, or who simply did not check, and it reaches them in English and Swahili on any handset.
A call also gives you something a published schedule cannot: a timestamped record that this customer was notified.
A planned-outage notice is a scheduled, area-scoped voice broadcast with three building blocks.
Area-scoped contact lists. Your source of truth maps customers to network topology: feeder, transformer, or district metered area for water. Each planned interruption resolves to one or more areas, and each area resolves to a phone list.
Schedule windows. Notices should land 24 to 48 hours before the interruption, during the day. Place calls from a worker you schedule yourself (the pattern below). If you want customers to be able to ask questions, use a broadcast with an AI agent: POST /v1/broadcasts takes schedule_days, daily_start_minute and daily_end_minute, timezone: "Africa/Nairobi", and max_attempts, backoff_minutes and retry_on for automatic retries of unanswered and busy calls.
The call itself. For a fixed notice, a plain voice-actions response is enough: say the schedule in both languages, offer a replay, offer an opt-out. Point your caller number's voice_callback_url at the handler with PUT /v1/numbers/{id}/routing.
An outage list is concentrated in one area. If you place 5,000 calls at the same moment into one estate, you congest the cell sites your customers are on, collect a pile of busy results, and get a wave of confused callbacks. Pace the broadcast:
// notify-feeder.js: pace a planned-outage broadcast. 5 workers, about 2 calls a second.const CONCURRENCY = 5;const GAP_MS = 2500; // per-worker gap between callsasync function callCustomer(msisdn, outageId, pass = 1) { const res = await fetch("https://api.sautikit.com/v1/calls", { method: "POST", headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`, "Content-Type": "application/json", // Re-running this pass cannot call anyone twice for 24 hours. // A deliberate retry pass carries a new pass number, so it dials. "Idempotency-Key": `outage:${outageId}:${msisdn}:p${pass}`, }, body: JSON.stringify({ from: "+254720000000", to: [msisdn] }), }); if (res.status === 402) throw new Error("Wallet empty. Top up, then re-run."); if (!res.ok) throw new Error(`call failed: ${res.status}`); return res.json(); // { call_id, status: "ringing", ... }}async function notifyArea(contacts, outageId, pass = 1) { // Remove duplicates from overlapping area lists before dialling. const queue = [...new Set(contacts.map((c) => c.msisdn))]; const workers = Array.from({ length: CONCURRENCY }, async () => { while (queue.length) { const msisdn = queue.shift(); await callCustomer(msisdn, outageId, pass); await new Promise((r) => setTimeout(r, GAP_MS)); } }); await Promise.all(workers);}
Five workers placing one call each every 2.5 seconds is about 120 calls a minute, so a 5,000-line feeder clears in roughly 40 minutes. Tune the concurrency to the size of the area. Network realities across Kenya covers how concentrated dialling behaves on each carrier.
Outage areas overlap. A substation job takes out three feeders, an estate sits on two lists, a customer has two service points on one phone number. Use two layers of protection:
In your list: remove duplicate numbers per outage event before dialling (the Set above).
On the API: send an Idempotency-Key header built as outage:{event_id}:{msisdn}:p{pass}. A duplicate request, whether from a crashed script that is re-run, a retried scheduler, or the same customer on two lists, gets the original response back with X-Idempotency-Replayed: true and no second call is placed. A key is remembered for 24 hours.
Include the pass number in the key. A deliberate retry pass is a new attempt, and without the pass number the 24-hour window would treat it as a repeat and not dial. Use one event_id for one physical interruption, however many areas it touches.
The handler below serves the whole call. The first request plays the notice and collects one digit. Sautikit then POSTs to the same URL with dtmfDigits set, and the handler acts on it.
import express from "express";const app = express();app.use(express.urlencoded({ extended: true })); // the voice callback delivers JSON (see below)app.use(express.json());const goodbye = { say: { text: "Asante. Goodbye." } };app.post("/voice/outage", async (req, res) => { const digits = req.body.dtmfDigits ?? req.body.digits ?? ""; if (!digits) { return res.json({ actions: [ { getDigits: { numDigits: 1, timeout: 6, // seconds nested: [ { say: { text: "Habari. This is a planned maintenance notice from your electricity provider. " + "Electricity in the Kariobangi area will be interrupted on Tuesday, " + "sixth October, from nine in the morning to five in the evening. " + "Stima itakatika Jumanne tarehe sita Oktoba, saa tatu asubuhi " + "hadi saa kumi na moja jioni. " + "To hear this again, press 1. To stop receiving these calls, press 9.", }, }, ], }, }, goodbye, { hangup: {} }, ], }); } if (digits === "1") { // Replay: the new request has no digits, so the notice plays again. return res.json({ actions: [{ redirect: { url: "https://alerts.example.co.ke/voice/outage", method: "POST" } }], }); } if (digits === "9") { // On an outbound call, clientDialedNumber is the customer you called. await saveOptOut(req.body.clientDialedNumber); return res.json({ actions: [ { say: { text: "You will no longer receive these calls. Umeondolewa." } }, { hangup: {} }, ], }); } return res.json({ actions: [goodbye, { hangup: {} }] });});
Replace "your electricity provider" with your company name and say it first: an unknown number reading out a schedule sounds like a scam until it identifies itself. State the area and the time window in both English and Swahili, and keep the notice under 40 seconds. Calls are billed per second after the customer answers, so a shorter notice also costs less.
When the crew closes the job, run a second, shorter broadcast to the same list: "Power in Kariobangi has been restored. If you still have no electricity, press 1." Each press of 1 is a still-out report from a known customer on a known transformer, received minutes after the power came back. Pass that list to the crew before they leave the area, and they can fix a missed fault on the same visit.
The two calls also bracket the outage in your own records: a notice before, a restoration call after.
Outage notices to your own account holders are service communication, not marketing, but Kenya's Data Protection Act, 2019 still applies to the personal data involved.
Basis and records. Notification uses the phone number the customer gave you at connection. Record where each number came from. Shipping compliant voice in Kenya covers the wider picture.
Offer an opt-out anyway. Press 9 costs one line of the menu. Apply it to all outage campaigns for that number straight away, and log the call that carried it.
Let people opt back in. Publish an inbound line where a customer can re-subscribe or update their number with one keypress. Incoming calls are free on Sautikit.
Tenants and account holders. The person who suffers the outage is often not the registered account holder, as with landlord accounts and shared meters. Let customers register extra alert numbers. Estates and facility managers will do it in bulk.
If a regulator, a customer dispute or an internal review asks "did you notify this customer?", you want a record per call. There are two sources.
Per-number events. Set events_url on your caller number's routing to receive call.started, call.answered, call.completed and call.failed. The event kind is in the X-Sautikit-Event-Kind header, and the JSON body carries the call's fields at the top level, including call_id, status, duration_seconds, answered_at and ended_at. Verify the X-Sautikit-Signature header, skip repeats using X-Sautikit-Event-Id, and store events against the outage. The webhooks page covers retries.
The call log.GET /v1/calls filters by status and time window, so "who did we reach for outage X" is one query per campaign.
Outcome
What it shows
Follow-up
Completed, 25 seconds or more
Notice delivered and heard through
None
Completed, under 10 seconds
Answered, probably hung up early
Call again in the next window
Not answered
Attempted, not reached
Retry in the next window
Busy
Attempted while the line or cell was busy
Retry with a wider pacing gap
Failed
A problem with the number
Flag the record for clean-up
A signed, timestamped call.completed event with a 32-second duration, tied to a customer account and an outage ID, lets you say "we notified this customer at 10:42 on the 4th" and show the record.
Outbound calls are KES 3.00 a minute, billed per second (KES 0.05 a second) after the call is answered. Unanswered calls cost nothing. A number is KES 500 a month. Assume a 30-second notice, 60% of customers answering on the first pass and 40% of the rest answering on a retry:
Line item
Arithmetic
Cost
First pass: 60% of 5,000 answer
3,000 × 30 s × KES 0.05
KES 4,500
Retry pass: 40% of the 2,000 missed
800 × 30 s × KES 0.05
KES 1,200
Unanswered attempts
Not charged
KES 0
Caller number, one month
Flat
KES 500
Total for the campaign
3,800 of 5,000 reached (76%)
KES 6,200
That is KES 1.50 per customer reached plus the number, with a delivery record for every attempt. A 15-second restoration pass to the same 3,800 customers adds KES 2,850. The wallet is prepaid in KES and topped up by M-Pesa or card, so about KES 9,000 covers both passes for the whole feeder. Full rates are on the pricing page.
The same pattern works for water. A supply interruption scoped to a district metered area is the feeder pattern with valves in place of transformers, and a courtesy call before a disconnection for non-payment costs less than sending a crew to disconnect and reconnect.
Estate and facility managers run the same calls for 200 to 2,000 households: water-trucking schedules, generator maintenance windows, borehole pump repairs. At that size a 30-second notice to 500 households costs KES 750 if every one of them answers.
To run your next planned-outage notice this way, claim a Kenyan number for KES 500 a month, top up by M-Pesa or card, and test the handler above on a short list first.