Sharif T. Issah
← All work
Case study 01Platform live · Android APK · not on Play Store

SAIBA

A WhatsApp-first assistant that answers customers from a business’s own knowledge and hands over to a person when it should.

Role
Backend, web, mobile and deployment, built under Sharif Technologies
Scale of code
About 15,000 lines of Python in the app package; 31 backend test files
Source
Private repository
Stack
Python · FastAPI · PostgreSQL · Alembic · Qdrant · Gemini API · React + Vite · Tailwind 4 · Expo / React Native · Docker + Caddy · Paystack

Problem

Small businesses answer the same customer questions on WhatsApp all day, and a generic chatbot either invents answers or never lets a human step in. The product needs to answer from the business’s own policies and catalogue, know its limits, and escalate cleanly.

What I built

  • A retrieval-augmented assistant that ingests PDF, Word and spreadsheet files and answers from them through a vector store.
  • A WhatsApp channel adapter, plus OAuth readers for Facebook, Instagram, TikTok and YouTube feeds.
  • Escalation rules so a conversation reaches a person when the assistant should not answer, with overdue follow-up tracking.
  • Commerce hooks and Paystack payment routing, with plan tiers (Starter, Growth, Scale) that gate features from one place.
  • Authentication with passkeys (WebAuthn), Google Sign-In and web push notifications.
  • A React dashboard, an Expo mobile app, Docker packaging behind Caddy, and APK releases published automatically to a public repository.

Architecture

Backend folder map, from the project’s ARCHITECTURE document

app/
  core/            config, db session, hashing
  models/          SQLAlchemy models by domain
  billing/         tiers + the one feature gate
  ai/              RAG engine, provider resolver
                   (knows nothing about WhatsApp or Shopify)
  integrations/
    base.py        ChannelProvider · CommerceProvider · SocialProvider
    registry.py    the only file that lists every integration
    channels/whatsapp/
    commerce/      shopify · woocommerce · custom (stubs)
    social/        facebook · instagram · tiktok · youtube
  api/routers/     auth, bots, admin, support
  main.py          assembles the app, no business logic

Shopify, WooCommerce and custom-shop commerce providers are stubs behind the contract. They are not shipped integrations.

Engineering decisions

Three contracts instead of one growing file

The original service lived in one 1,000-line main module. Every integration is now a ChannelProvider, CommerceProvider or SocialProvider registered in one place, so a Shopify bug cannot break WhatsApp.

Billing is a single dependency, not scattered checks

Routes declare require_feature("name"). Changing a plan or gating a new feature touches the tier table only, never integration code.

Explicit model-provider resolution

A tenant’s own key is used first, then their backup key, then a platform fallback only if one is really configured. Otherwise the request fails with a clear configuration error. A local embedding fallback was removed because it bypassed that routing.

Migrations over startup ALTERs

The first version patched its schema with ALTER TABLE statements at every boot. The schema is now versioned with Alembic and applied by the container entrypoint.

Current state and limits

  • The Android app is distributed as APK releases because it is not on Google Play yet.
  • Commerce providers beyond the custom path are stubs.
  • I could not load the live site from my research environment, so its availability is stated from the project’s own documentation.