A peer-to-peer voice learning platform for Qur’an students and verified teachers in Ghana. Designed and built by Kausara Yahaya Kpabia. The constraints (privacy, gender, payments, and community trust) shape every layer of the system.
Ghana has millions of Muslims who want to improve their Qur’an recitation, and a community of teachers who already give that instruction informally. There was no platform that could safely and respectfully connect the two: paid tutoring apps don’t fit the religious context, and generic voice apps don’t solve gender segregation, payment access, or trust.
Tilawa is that platform. It is free to learn, supported by donations, never records audio, enforces gender segregation inside the database, and is tuned for the network conditions of a cheap Android phone on 3G.
Audio is ephemeral by design. Sessions run over peer-to-peer WebRTC, with no server in the audio path except a TURN relay when NAT traversal fails. Nothing is recorded. There is no playback surface, no storage bucket, no analytics on call content. The architecture itself makes recording impossible, not just disallowed.
The cultural and religious requirement is absolute: sisters learn from sisters,
brothers from brothers. UI filtering on its own would not be sufficient, and would
not survive a single bug. Gender is stored on profiles as an immutable
column. Every cross-user table (session requests, sessions, reviews, favourites,
reports) carries Postgres Row-Level Security policies that gate access on matching
gender. The mobile client could be replaced tomorrow and the invariant would still hold.
Donor plans are denominated in GHS, stored as numeric(10,2), and
integrated with Paystack for mobile money and card. Every webhook is idempotent:
a duplicate delivery from Paystack cannot double-credit a wallet. Every client
mutation can be safely retried. Every Edge Function response uses a uniform
{ ok, data | error } shape, so clients have one contract to handle.
Most P2P WebRTC tutorials assume a forgiving network and modern hardware. The reality in Ghana is a Tecno Spark with 2 GB of RAM on an unreliable 3G connection, and an OS that will cheerfully kill the app if it backgrounds for thirty seconds. I built reconnection logic that handles ICE failure mid-call, a foreground service that keeps Android from collecting the process, and CallKit integration on iOS so calls survive the lockscreen the way users expect phone calls to. Signalling rides Supabase Realtime. SDP and ICE exchange complete in under a second on a warm connection.
Edge Functions enforce auth and idempotency, but they are defence in depth. The
database itself refuses to return rows that a user is not entitled to see. No read
path in the mobile app uses the service_role key. The threat model
assumes an attacker with a valid auth token, so the system is designed to make every
cross-user access an explicit decision in SQL, not an implicit assumption in TypeScript.
Religious software depends on community trust. Verified-teacher badges, a “no recording” statement surfaced inside the call UI, a reporting flow with a human moderator in the loop, and an admin console for review are not afterthoughts. They are the reason users return.
Most of the engineering work on Tilawa lives in the parts the user never sees: the policies that refuse the wrong row, the webhook that will not double-charge, the reconnection that quietly hides a dropped packet.
Choosing Supabase, instead of rolling auth, Postgres, and Realtime separately, saved months. Using Riverpod with strict layering (data, domain, application, presentation) kept a 50k-line codebase navigable for one engineer. Writing pgTAP tests for every RLS policy turned what could have been a Friday-night security incident into an unremarkable green CI run.
I would invest in automated tests from week one, not week sixteen. A second engineer reviewing Postgres migrations would have been worth it; some things only look obvious in retrospect. And I would commit to a single design system earlier. The cost of revisiting components is paid in months, not days.