A multi-tenant SaaS for AI-powered WhatsApp customer communication, orders, and marketing — buildable and fully usable without a live WhatsApp Business account.
Every module in the original spec has working backend code and a Prisma schema wired for it. Three integrations use a swappable provider pattern so you can develop and demo the whole product before connecting any external account, then flip a switch later with zero code changes:
| Integration | Mock (default) | Live |
|---|---|---|
In-memory simulated sends; /whatsapp/simulate/inbound lets you send a message "as a customer" from the Inbox UI |
Real Meta Cloud API — enter 5 credentials in Settings > WhatsApp Setup | |
| AI replies | Deterministic keyword/intent matcher, grounded in uploaded knowledge chunks | OpenAI (gpt-4o-mini + embeddings) — set AI_MODE=live and OPENAI_API_KEY |
| Payments | Instant simulated checkout links, dev-only "complete payment" endpoint | Paystack / Flutterwave / Stripe — set the relevant secret key env var |
This is the piece that matters most for how you'll use this: the switch is a database row, not a
code path. Every business has a WhatsAppConfig record with a mode field (MOCK | LIVE).
WhatsAppService.resolveProvider(businessId)is the only place that decision is made.- Every other module — Inbox, Automation, AI, Orders, Broadcast, Notifications — calls
WhatsAppService.sendText(businessId, ...)and never touches a provider directly. - Submitting the 5 credentials via Settings > WhatsApp Setup (
POST /whatsapp/connect) sets that business'smodetoLIVEand encrypts the credentials at rest. The very next outbound message for that business goes through the real Meta Graph API. No redeploy, no env var changes, no touchingWhatsAppModuleor any consumer of it. - You can revert to mock at any time from the same screen.
The same pattern is used for AI and Payments providers, so the whole platform can go from "fully simulated demo" to "connected to real accounts" incrementally, integration by integration.
apps/api/ NestJS backend — see src/modules/* for one folder per domain
apps/web/ Next.js frontend — dashboard, inbox, settings
infra/docker/ docker-compose.yml for local Postgres + Redis + both apps
docs/ architecture.md (see the earlier blueprint conversation)
# 1. Install dependencies
cd apps/api && npm install
cd ../web && npm install
cd ../..
# 2. Environment
cp .env.example .env
# defaults are fine for mock mode — nothing external required
# 3. Database (Postgres + Redis via Docker)
docker compose -f infra/docker/docker-compose.yml up -d postgres redis
# 4. Migrate + seed
cd apps/api
npx prisma migrate dev --name init
npm run prisma:seed
cd ..
# 5. Run both apps (two terminals)
npm run dev:api # http://localhost:4000/api/v1
npm run dev:web # http://localhost:3000Log in with the seeded demo account: owner@demo.local / Owner123!
(Super admin: admin@platform.local / Admin123!)
Go to Inbox and use the "Send as customer" simulator — try typing "what's the price?" and watch the keyword automation (or AI fallback) reply instantly, exactly as it would over real WhatsApp.
- Create a Meta App with the WhatsApp product enabled, get your Phone Number ID, WABA ID, a permanent Access Token, and your App Secret.
- In the app, go to Settings > WhatsApp Setup, fill in the 5 fields shown there (the 5th, Webhook Verify Token, is one you choose yourself).
- In the Meta App dashboard, set your webhook URL to the one shown on that same screen
(
/api/v1/whatsapp/webhook/<your-business-id>) and use the same verify token. - Click Connect & go live. Nothing else in the app changes.
Dashboard > AI Knowledge accepts PDF, DOCX, TXT, MD, or CSV files (drag-and-drop, up to 20MB).
Files are stored via StorageService — local disk by default (./uploads, zero setup), swappable
to S3 with no code changes by setting STORAGE_MODE=s3 plus the S3_* env vars. Text is extracted
(pdf-parse / mammoth), chunked, and indexed for the AI to retrieve from when answering
customers. Retrieval currently uses keyword scoring rather than vector similarity — see
PROJECT_PLAN.md for the pgvector embedding write that's still pending.
Redis-backed queues handle anything that shouldn't block the caller or that needs retry-on-failure:
broadcast— campaign fan-out is rate-limited (20 messages/sec) so a send to a large audience can't blow past WhatsApp API limits; the campaign status flips toSENTautomatically once every recipient job completes.automation-actions— event-triggered automations (e.g. "on payment confirmed, send a receipt") are executed here. Previously these were only detected, not run — seePROJECT_PLAN.mdPhase 4 for the fix.notifications— everyNotificationsService.send()call is queued and retried with backoff on failure rather than dispatched inline.WHATSAPPchannel notifications route through the realWhatsAppService(so they respect that business's mock/live mode);IN_APPpushes a real-time event over the WebSocket gateway;EMAIL/SMSare simulated (logged) pending a real provider (SES/Twilio) — same one-line-swap shape as the rest of the codebase.
Keyword-triggered automations (the "if customer says 'price'" kind) stay synchronous by design — the reply is needed inline for the same webhook/simulator call that triggered it.
See PROJECT_PLAN.md for the full phase-by-phase checklist of what's built vs. outstanding.
Below is a snapshot of what's stubbed as of the current phase — check the plan for anything not
listed here.
- Analytics rollup as a scheduled/repeatable job
- Real pgvector embedding storage (chunks are stored; the embedding column write is a one-line
addition once you're on a live AI provider — see comment in
ai.service.ts) - 2FA, PDF/Excel report export, audit log UI, Super Admin dashboard UI (API exists, no frontend yet)
- Automated tests
None of these block using or demoing the app — they're the natural next slice of work.