Skip to main content

Textify Voice: Product & Functional Specification

Status: draft for implementation · Owner: Joshua Saimon Sakweli (QBIT SPARK) Companion doc: ARCHITECTURE.md (components, data model, APIs, infra). This file is written so a new engineer, or a new AI coding session, can pick up the work with no prior context.


1. Background

Textify Africa (textify.africa) is an existing bulk SMS + WhatsApp platform, written in Go, live in Tanzania, Uganda and Nigeria. Its main model is white-label: agencies and resellers ("partners") brand the platform as their own, set their own prices, and onboard their own customers. Customers top up a prepaid wallet with mobile money or card.

We are adding a third channel: a call center / business phone line product, similar in spirit to SemaCall (semacall.com): "One business. One number. Every team." Customers call one number; the call reaches the right person (or an AI answers) in the customer's language.

The voice capability is built as a separate service ("Voice Service") in Java Spring Boot. Textify consumes it as a client. The Voice Service must not know about Textify's users, auth or money.

2. Goals

  1. A Textify customer can get a Tanzanian phone number and have every incoming call answered, by an AI receptionist (Swahili first, English supported) or by human agents in the browser.
  2. AI can hand the caller over to a human when needed.
  3. Every call is logged, optionally recorded, transcribed and summarized.
  4. Missed calls trigger an automatic SMS/WhatsApp follow-up (Textify already does messaging).
  5. Usage is billed per minute from the existing Textify wallet.
  6. The Voice Service is reusable by other QBIT SPARK products later (JikoXpress, Mauzodukani, NexGate) without changes.

3. Non-goals (for now)

  • No video calls over phone lines (LiveKit SIP does not support video over SIP anyway).
  • No user accounts, roles, plans or pricing inside the Voice Service.
  • No approval workflows anywhere. Keep everything self-service and simple.
  • Countries other than Tanzania (Uganda/Nigeria later, using the same design).
  • Building our own telco connection. We use Africa's Talking for numbers and the PSTN.

4. Key terms (plain language)

Term Meaning
PSTN / telco network The normal phone network (Vodacom, Airtel, Yas, Halotel, TTCL).
SIP The protocol internet phones use to set up calls (ring, answer, hang up). Like HTTP but for calls. Voice audio travels separately over RTP.
SIP account One login for one phone/softphone (like an email account but for calls).
SIP trunk A connection carrying many calls at once between two systems over SIP. Here: Africa's Talking ⇄ our LiveKit.
Human agent A staff member who answers calls (in the browser app).
AI agent A bot that talks on the phone: listens → thinks → speaks.
STT / LLM / TTS Speech-to-text, the language model (the "brain"), and text-to-speech (the voice).
Realtime / speech-to-speech model One model that hears and speaks directly (e.g. OpenAI Realtime).
LiveKit room A live audio session. Each phone call = one room. The caller joins as a "SIP participant"; AI or humans join the same room.
Dispatch rule LiveKit config that decides which room an incoming call goes to and which AI agent joins.
DTMF Keypad presses during a call ("press 1").
Cold / warm transfer Cold: just forward the call. Warm: AI briefs the human first, then hands over.

5. Providers we use and why

Provider Role Why
Africa's Talking (AT) Tanzanian phone numbers + connection to all TZ telcos + SIP trunk Local numbers and licensing. LiveKit's own phone numbers are US-only.
LiveKit (self-hosted, open source) Media server: rooms, SIP, browser audio, recording, transfers Already used in production by us for NexGate (video, spaces, audio calls). Self-hosted = no per-minute LiveKit fee.
OpenAI AI brain + voice (Realtime model), transcription, summaries Best overall quality. Swahili quality must be tested; TTS can be swapped.

6. Costs (Africa's Talking Tanzania, as published on africastalking.com/pricing, Oct 2026)

Item Price
Regular number (Airtel) Setup TZS 100,000 + 18% VAT; TZS 35,000/month + 18% VAT
Toll-free number Same as regular, plus TZS 170,000 deposit
Regular incoming TZS 17 / min
Regular outgoing TZS 60 / min
Toll-free incoming TZS 200 / min
SIP agent leg TZS 4.30 / min
SIP trunk TZS 8.61 / min
Conference TZS 8.61 / min
TTS (Google, via AT) per character

6.1 How the costs combine

Every call has leg 1 (customer ⇄ our AT number) and, if someone answers, leg 2 (our number ⇄ whoever answers).

Customer ──leg 1──> AT number ──leg 2──> answerer
          17 in / 60 out          SIP trunk to LiveKit: 8.61
                                  forward to a mobile: 60   (avoid)
Scenario Approx. cost per minute
Inbound call into LiveKit (AI or browser agent) 17 + 8.61 ≈ TZS 25.61 + AI cost if AI answers
Outbound call from LiveKit to a customer 60 + 8.61 ≈ TZS 68.61 (to confirm)
Inbound forwarded to a staff mobile (fallback only) 17 + 60 ≈ TZS 77

Key facts already agreed:

  • Cost follows call minutes, not the number of agents. Idle agents cost nothing, so customers can add unlimited agents.
  • Avoid forwarding to mobiles. Agents answer in the browser/app.
  • Design decision: money is not the constraint, so all calls go through the SIP trunk into LiveKit (one call path, simpler code). We don't split IVR onto AT XML to save the 8.61.

6.2 AI cost (on top of telco)

An AI-answered minute adds OpenAI cost (Realtime, or STT + LLM + TTS). This is several times the telco cost per minute. Exact per-minute figures must be measured during the PoC from real calls (token/audio usage logged per call).

6.3 Indicative selling prices (suggestion, to be finalized after PoC)

Item Suggested price
Number rental TZS 60–75k / month
Inbound minute, human-answered TZS 40–50 / min
Outbound minute TZS 90–100 / min
AI-answered minute TZS 150–250 / min
AI summary add-on + TZS X / min (set after measuring)

Pricing lives in Textify only. The Voice Service only reports usage.

7. Users & personas

  • Business owner / admin (Textify customer): gets a number, sets the AI greeting and business info, adds agents, sees calls, recordings and summaries.
  • Human agent: logs into the Textify web app, sets themselves available, answers calls in the browser, makes click-to-call outbound calls.
  • Caller: a member of the public calling the business number from any TZ network.
  • Partner (reseller): later phase. Resells voice under their own brand (same white-label model as SMS).

8. Functional requirements

8.1 Numbers

  • FR-N1: Textify admin (QBIT/Textify ops) obtains numbers from AT and registers them in the Voice Service.
  • FR-N2: A number is assigned to exactly one account (Textify's tenant id, opaque to the Voice Service).
  • FR-N3: Each number has an answer mode: AI_FIRST (AI answers, can hand over), HUMAN_ONLY (ring agents), AI_ONLY.
  • FR-N4: Reassigning or releasing a number is a single API call. No approval.

8.2 Inbound calls

  • FR-I1: Caller dials the number → call arrives over the SIP trunk → LiveKit creates a room → Voice Service records a call RINGING.
  • FR-I2: Before answering, the Voice Service asks Textify "can this account take a call?" (prepaid balance). If Textify is unreachable, allow the call (fail-open) and bill later.
  • FR-I3: If the account can't pay: play a short polite message and end the call (no AI cost).
  • FR-I4: Play a recording consent notice when recording is on ("Simu hii inaweza kurekodiwa kwa ajili ya ubora wa huduma"; in English: "This call may be recorded for quality of service").
  • FR-I5: AI_FIRST / AI_ONLY → AI receptionist joins and greets with the account's greeting.
  • FR-I6: HUMAN_ONLY → ring the account's available agents (ring group). If nobody answers within N seconds (default 30) → voicemail or AI fallback (configurable) → mark MISSED if nobody handled it.
  • FR-I7: Caller keypresses (DTMF) are supported for simple menus ("press 1 for sales").

8.3 AI receptionist

  • FR-A1: Speaks Swahili first, switches to English if the caller does. Handles code-switching (Swahili and English mixed).
  • FR-A2: Uses the account's instructions (business info, FAQs, hours, tone) supplied by Textify.
  • FR-A3: Short, spoken-style answers. Never invents prices or policies. If unsure → offer a human.
  • FR-A4: Tools the AI can use (v1): transfer_to_human, take_message, end_call. Later: account-defined tools (check order, book appointment) via webhooks to the client app.
  • FR-A5: Handover: AI calls transfer_to_human → Voice Service rings available agents → first to accept joins the room → AI briefly introduces the context (warm transfer), then leaves.
  • FR-A6: If no agent is available: AI takes a message (name, number, reason) → stored on the call → event to Textify.
  • FR-A7: Barge-in/interruptions and turn detection are handled by LiveKit Agents.

8.4 Human agents (browser)

  • FR-H1: Agents are identified only by an opaque agentRef from Textify. Textify owns the user, login and role.
  • FR-H2: Agent sets status AVAILABLE / BUSY / OFFLINE through Textify, which forwards it to the Voice Service.
  • FR-H3: Incoming call → available agents in the group get a ring in the browser (data message over LiveKit). Accept → join the call room. Reject or timeout → next.
  • FR-H4: Ring strategy: ALL_AT_ONCE (default) or ROUND_ROBIN.
  • FR-H5: Agent can mute, hold, hang up, and transfer to another agent.
  • FR-H6: Click-to-call outbound from the dashboard: agent clicks a number → Voice Service dials out via the trunk → agent and customer in one room.
  • FR-H7: Fallback transfer to an external mobile is allowed (costs about TZS 60/min). Off by default.

8.5 Recording, transcription, summary

  • FR-R1: Recording on/off per number (default on, with consent notice).
  • FR-R2: After the call: audio file stored in our object storage (never rely on provider links).
  • FR-R3: Transcription (Swahili/English) → summary containing:
    • 2–3 line summary
    • customer intent, resolved yes/no
    • follow-up actions
    • sentiment (positive / neutral / negative)
    • topic tag (sales, support, complaint, inquiry, other)
  • FR-R4: Transcript + summary attached to the call record; event summary.ready sent to the client app.
  • FR-R5: Retention: recordings deleted after 90 days by default (configurable per account). Tanzania Personal Data Protection Act applies.
  • FR-R6: Summary is a billable add-on (Textify decides price; Voice Service reports summaryMinutes).

8.6 Missed calls & follow-ups

  • FR-M1: A call is MISSED if no human or AI handled it (e.g. HUMAN_ONLY with nobody answering and no fallback, or the caller hung up while ringing).
  • FR-M2: Voice Service emits call.missed with the caller number. Textify sends the SMS/WhatsApp follow-up ("Samahani tumekukosa, tutakupigia tena"; in English: "Sorry we missed you, we'll call you back") using its existing messaging.

8.7 Usage & billing

  • FR-B1: On every call end, the Voice Service emits call.ended with usage broken down by leg (seconds): inboundSeconds, outboundSeconds, trunkSeconds, aiSeconds, externalTransferSeconds, recorded (bool), summarySeconds.
  • FR-B2: Textify prices and deducts from the wallet. The Voice Service never deals with money.
  • FR-B3: Events are idempotent (event id) and retried until Textify returns 2xx.

8.8 Dashboard (built in Textify, data from the Voice Service)

  • Call log: time, direction, caller, number, handled by (AI / agent), duration, status, cost (Textify-computed).
  • Call detail: audio player, transcript, summary, AI message taken.
  • Number settings: answer mode, greeting, AI instructions, recording on/off, ring group, timeout, fallback.
  • Agents: availability, calls handled.
  • Simple stats: calls per day, answered vs missed, AI-handled vs human, average duration.

9. Non-functional requirements

Area Requirement
Latency AI response start < ~1 s after the caller stops speaking (target). Call connect < 3 s.
Availability Voice Service must keep taking calls if Textify is down (fail-open balance check, queued webhooks).
Isolation Dedicated LiveKit box for Textify Voice. Not shared with NexGate.
Security SIP/RTP ports open only to Africa's Talking IPs. Service tokens between apps. Signed webhooks. No user PII beyond phone numbers.
Privacy Consent notice, retention policy, recordings in our storage, access only via the client app.
Language Swahili first-class. Test quality with real Tanzanian callers before selling AI.
Simplicity No approval workflows, no internal OAuth, minimal moving parts.

10. Phases

Phase 0: Proof of concept (goal: one real call end-to-end)

  1. Confirm with AT (see §11). Get one TZ number + SIP trunk.
  2. Dedicated box: LiveKit server + LiveKit SIP + Redis. Firewall restricted to AT.
  3. Inbound trunk + dispatch rule → Spring Boot dials OpenAI Realtime into the room over SIP → AI greets in Swahili.
  4. Measure: latency, audio quality, OpenAI cost per minute.
  5. Swahili test: 10–20 real people (Swahili, English, mixed). Judge understanding and voice. Swap TTS if the voice is weak.

Phase 1: MVP (sellable)

  • Spring Boot Voice Service: accounts, numbers, config, calls, events, LiveKit integration.
  • AI receptionist with handover to browser agents.
  • Browser agent client (in Textify's web app) + ring groups + click-to-call.
  • Recording + storage + retention.
  • Balance check + call.ended usage events → wallet deduction in Textify.
  • Missed-call SMS/WhatsApp.

Phase 2

  • Transcripts + AI summaries (if not already in MVP).
  • Account-defined AI tools (webhooks to the client app: check order, booking).
  • Voicemail, business hours / after-hours behaviour.
  • Stats dashboard.

Phase 3

  • Voice broadcast (recorded message to a contact list: "bulk voice", fits Textify's bulk DNA). Check TZ calls-per-second limits.
  • AI outbound campaigns (reminders that talk back).
  • White-label voice for partners.
  • Other countries (Uganda/Nigeria) through AT.
  • Other QBIT products as clients of the Voice Service.

11. Open questions for Africa's Talking (must answer before Phase 1)

  1. Can the TZ SIP trunk route inbound calls for our number to an external SIP URI (our LiveKit SIP endpoint)? Note: LiveKit SIP does not support SIP REGISTER, so AT must send calls to us directly.
  2. Can it accept outbound calls from our server IP (and what auth: IP allowlist or username/password)?
  3. What are AT's SIP signalling and media IP addresses (for our firewall allowlist)?
  4. Exact billing for inbound to trunk: 17 + 8.61? Any extra?
  5. Exact billing for outbound via trunk: 60 + 8.61? Any extra?
  6. Is the SIP agent TZS 4.30 charged on inbound calls too? Any setup/monthly fee for SIP agents?
  7. Are unanswered rings to SIP endpoints billed? 7b. The listed regular number is Airtel. How do callers on Vodacom, Yas, Halotel and TTCL reach it, and at what cost to them and to us?
  8. Calls-per-second (CPS) limits and pricing in Tanzania (needed for broadcast).
  9. Supported codecs (G.711 PCMU/PCMA expected) and DTMF method (RFC 2833/4733).
  10. Does reselling voice to our customers need any TCRA licence on our side, or is AT's licence enough? Get it in writing.
  11. Number porting / getting several numbers quickly; toll-free availability.

AT is not on LiveKit's list of tested SIP providers (Twilio, Telnyx, Exotel, Plivo, Wavix, Sinch, didlogic). LiveKit is designed to work with any SIP provider, so the PoC must prove compatibility.

12. Risks

Risk Mitigation
AT trunk can't deliver to an external SIP URI Verify first (§11 Q1). Fallback: AT Voice XML + AT SIP agents for humans; AI limited.
Swahili STT/TTS quality poor Early real-user test; swap TTS provider; keep human fallback prominent.
AI cost higher than expected Measure in PoC; short answers; pre-recorded greetings; cheap model for simple turns; price AI minutes higher.
SIP fraud/attacks Firewall to AT IPs only; LiveKit inbound trunk allowed_addresses + numbers.
Textify outage blocks calls Fail-open balance check; queued + retried webhooks.
Regulatory (TCRA, PDPA) Written confirmation from AT; consent notice; retention policy.
Single box failure Phase 1 single box is OK; add a second node (LiveKit supports multi-node via Redis) when volume grows.

13. Decisions log

# Decision
D1 Numbers and telco via Africa's Talking; media via self-hosted LiveKit; AI via OpenAI.
D2 All calls go through the SIP trunk into LiveKit (no split IVR on AT XML), for one call path.
D3 Agents answer in the browser (no forwarding to mobiles by default).
D4 Dedicated LiveKit box for Textify Voice, separate from NexGate's LiveKit.
D5 Separate Voice Service in Java Spring Boot. Knows no users/auth/money; owns numbers, routing, calls, recordings, usage events.
D6 Textify (Go) owns users, tenants, partners, wallet, pricing, dashboard, messaging follow-ups.
D7 Preferred: no Python. OpenAI Realtime joins the LiveKit room as a SIP participant, controlled from Spring Boot via OpenAI's call-control REST + WebSocket (ARCHITECTURE §2.5). Fallback: thin Python LiveKit Agents worker (§2.2) if OpenAI SIP interop or Swahili voice quality fails the PoC.
D8 Billing is per minute from the Textify wallet; Voice Service emits usage events only.
D9 Recording default on with consent notice; 90-day retention default.
D10 No approval workflows anywhere.