Skip to content
M. SALL
Work

Professional engagement · March 2025

KÀLAMA

A West African EdTech platform: a course marketplace, live classes and mobile-money payments, built as microservices.

Problem
Existing online course platforms assume a bank card and a stable connection — two assumptions that are false in West Africa.
Outcome
Live in production at kàlama.com: a separate authentication service behind an Nginx gateway, BigBlueButton live classes, mobile-money payments through PayTech with asynchronous IPN confirmation, and an internal wallet.
Role
Lead full-stack developer
FastAPISQLAlchemy 2.0PostgreSQLRedisBigBlueButtonPayTechNginxDockerCloudflareboto3 / S3slowapiReact 18Material UIVite
KÀLAMA home page: "Excellence in learning, accessible to all", with entry points to the marketplace and replays.KÀLAMA course marketplace, showing the catalogue of available training.
KÀLAMA on mobile: the same home page adapted to a phone screen.
Screenshots of the platform in production.

What problem does KÀLAMA solve?

Building a course marketplace for the Senegalese market runs immediately into two constraints Western platforms do not have.

  • Payments. Bank cards are marginal. Mobile money (Orange Money, Wave, Free Money) is the real instrument, with its asynchronous flows, its webhooks and its intermediate states.
  • The network. A live class has to stay usable on an unreliable connection, and the replay is not a convenience — it is the safety net.

What architecture decisions were made on KÀLAMA?

Microservices behind an Nginx gateway, not a monolith. The payment service has a life of its own: it receives webhooks from external providers, with retries and states that resemble nothing else in the application. Isolating it stopped its complexity from contaminating the teaching domain.

A separate authentication service. auth/ has its own Dockerfile, its own dependencies and its own tests. That is not architectural zeal: authentication is the one component whose failure must stay contained, and the one you want to redeploy without touching the catalogue.

PayTech for payments. The aggregator covers the methods actually used in Senegal. Confirmation arrives as an IPN — an asynchronous notification from the provider — backed by webhooks. One router per channel, because those flows share neither the rhythm nor the guarantees of the rest of the application.

BigBlueButton rather than a proprietary SDK. Self-hostable, no per-minute cost, and control over adaptive quality — which matters when the participant's bandwidth is the unknown. Above all it brings what a generic video call does not: a whiteboard, presentation sharing, polls and breakout rooms — the gestures of a class, not of a meeting.

Roles in a single module. core/roles.py defines the role sets once. A literal ["formateur", "admin", …] copied across fifteen files always drifts eventually, and drift in an authorisation rule is a vulnerability.

Cloudflare in front. Static assets cached close to learners, TLS termination and abuse protection. Serving a West African education platform from a single origin means imposing that origin's latency on everyone.

How was KÀLAMA implemented?

The backend exposes twenty-one routers: sessions and session actions, media, packs and pack content, payments, PayTech, IPN, webhooks, wallet, admin finance, analytics, dashboard, invitations, sharing, notifications, feedback, and even an SEO router. JWTs through python-jose, passwords in bcrypt, rate limiting backed by Redis.

Session media go to object storage through boto3: a replay must never pass through the application's disk.

The React 18 frontend in strict TypeScript consumes that API, with Material UI as an accessible component base and Vite for a short development loop.

Everything runs in containers: each service has its image, Nginx routes traffic and terminates TLS, Cloudflare caches close to learners. A failover runbook is versioned alongside the code — writing the outage procedure before the outage is the only way it is ever useful.

What did KÀLAMA teach me?

The hard part was neither the video nor the payments taken separately, but the consistency between them: a participant has paid, the provider's webhook arrives three minutes late, and the class starts now. Making that intermediate state explicit in the data model — rather than treating it as an error — removed an entire class of bugs.

A project of the same calibre ?

Based in Thiès, available in dakar & remote. I reply within 48 hours.

Let's talk