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.