Reference
Architecture
One-page tour of the codebase — so you know where to look when you want to change something in your fork.
Stack
| Layer | Tool | Why |
|---|---|---|
| Rendering | Next.js 16 (App Router) | Server components for data-fetch pages; client components where interactivity's needed. React 19. |
| UI | Tailwind v4 + shadcn/base-ui primitives | Tailwind's zero-runtime styling, shadcn patterns for composable, dark-theme-first components. |
| Data + Auth | Supabase | Postgres with Row-Level Security, built-in email/password auth, Storage for avatars, Realtime for the inbox. |
| WhatsApp® | Meta Cloud API | Official Business API. No third-party gateway. |
| Encryption | node:crypto AES-256-GCM |
Per-user WhatsApp access + verify tokens at rest. |
| Scheduler | External HTTP pinger | Hits GET /api/automations/cron to drain Wait-step executions. |
No ORM, no GraphQL layer, no dedicated backend. The Next server-side
routes read and write Supabase directly via @supabase/ssr.
Folder layout
wacrm/
├─ src/
│ ├─ app/ Next.js App Router routes
│ │ ├─ (auth)/ login, signup, forgot-password
│ │ ├─ (dashboard)/ authenticated UI
│ │ │ ├─ dashboard/ home / metrics / activity feed
│ │ │ ├─ inbox/ shared inbox
│ │ │ ├─ contacts/ contacts + tags
│ │ │ ├─ pipelines/ Kanban deals
│ │ │ ├─ broadcasts/ campaign list + builder
│ │ │ ├─ automations/ flow builder + logs
│ │ │ └─ settings/ Profile / WhatsApp / Templates / Tags
│ │ ├─ docs/ this documentation, rendered
│ │ ├─ api/ JSON endpoints (server-only)
│ │ │ ├─ whatsapp/webhook/ inbound from Meta
│ │ │ ├─ whatsapp/send/ outbound message
│ │ │ ├─ whatsapp/broadcast/ bulk dispatch
│ │ │ ├─ whatsapp/config/ settings read/write
│ │ │ ├─ whatsapp/templates/ template sync
│ │ │ ├─ whatsapp/media/[id]/ media relay
│ │ │ └─ automations/ engine + cron + CRUD
│ │ ├─ page.tsx marketing landing
│ │ ├─ layout.tsx root layout, metadata, icon
│ │ ├─ opengraph-image.tsx dynamic OG image
│ │ └─ icon.tsx dynamic favicon
│ ├─ components/
│ │ ├─ landing/ marketing page sections
│ │ ├─ dashboard/ dashboard widgets
│ │ ├─ inbox/ conversation list, thread, composer
│ │ ├─ contacts/, pipelines/, broadcasts/, automations/, settings/
│ │ ├─ docs/ docs viewer shell
│ │ ├─ seo/ JSON-LD helpers
│ │ ├─ layout/ header, sidebar, shell
│ │ └─ ui/ shadcn/base-ui primitives
│ ├─ hooks/ use-auth, use-realtime, etc.
│ ├─ lib/
│ │ ├─ supabase/ client, server, middleware factories
│ │ ├─ whatsapp/ Meta API client, encryption, phone utils
│ │ ├─ automations/ engine, steps, validation, templates
│ │ ├─ dashboard/ queries + date utils
│ │ ├─ docs/ markdown loader for /docs/
│ │ ├─ seo/ site config, FAQ data, structured data
│ │ └─ rate-limit.ts per-user token bucket
│ ├─ types/ shared TypeScript types
│ └─ middleware.ts session-refresh middleware
├─ supabase/
│ └─ migrations/ idempotent SQL files, run in order
├─ docs/ you are here
├─ public/ static assets
└─ next.config.ts Cache-Control + security headers
Request lifecycle: inbound WhatsApp message
Meta Cloud API ──POST──▶ /api/whatsapp/webhook
│
├─ verifyMetaWebhookSignature (HMAC-SHA256)
│ └─ rejects 401 if META_APP_SECRET unset or wrong
│
├─ supabaseAdmin (service-role, lazy init)
│
├─ find contact by phone (create if new)
├─ find or create conversation
├─ insert message row
│
├─ fire runAutomationsForTrigger(...)
│ └─ matches automations on new_message / keyword
│
└─ 200 OK (Meta retries otherwise)
Realtime fan-out:
messages INSERT ──▶ supabase/realtime ──▶ inbox page subscription
└─ appends to thread without refetch
Request lifecycle: outbound message from the inbox
Composer ──fetch──▶ /api/whatsapp/send
│
├─ createClient() — user-scoped Supabase client
├─ auth.getUser() — 401 if unauthed
├─ checkRateLimit('send:<uid>', 60/min)
│
├─ select whatsapp_config row (RLS-scoped)
├─ decrypt(access_token)
│ └─ self-heal: upgrade CBC → GCM (fire-and-forget)
│
├─ sendTextMessage / sendTemplateMessage (Meta API)
├─ retry with phone-number variants on "not allowed"
│
├─ insert message row with status = 'sent'
└─ 200 + wamid
UI:
Optimistic temp row shown immediately.
Real row arrives via Supabase realtime → replaces temp.
If 429 / 5xx: temp row flips to status = 'failed'.
Security primitives
- Row-Level Security on every table. Users see only their own
rows via
auth.uid() = user_id. Service-role bypasses RLS; only server-only modules use it. - Encryption (
src/lib/whatsapp/encryption.ts) — AES-256-GCM for WhatsApp tokens at rest. Backward-compatible decrypt for legacy CBC rows; self-heals on first use. - Webhook signatures (
src/lib/whatsapp/webhook-signature.ts) — HMAC-SHA256 withMETA_APP_SECRET. Fail-closed if the env var isn't set. - Cron secret —
GET /api/automations/cronrequiresx-cron-secret: <AUTOMATION_CRON_SECRET>. Constant-ish string comparison. - Rate limiting (
src/lib/rate-limit.ts) — per-user fixed window, process-localMap. Swap for Redis/Upstash at horizontal scale. - HTTP headers (
next.config.ts) — HSTS, nosniff, X-Frame-Options: DENY, Referrer-Policy, Permissions-Policy, and a report-only CSP.
Where to change things
| Want to change… | Start here |
|---|---|
| Marketing copy / landing page | src/app/page.tsx + src/components/landing/* |
| Dashboard metrics | src/lib/dashboard/queries.ts + src/components/dashboard/* |
| Inbox behaviour | src/app/(dashboard)/inbox/page.tsx + src/components/inbox/* |
| Automation triggers / actions | src/lib/automations/engine.ts, steps-tree.ts, meta-send.ts |
| Add a DB column | new migration in supabase/migrations/NNN_*.sql, then update the matching src/types/*.ts |
| Change auth provider | src/lib/supabase/{client,server}.ts + src/hooks/use-auth.tsx + middleware.ts |
| Add a new API route | src/app/api/<name>/route.ts, pattern after existing routes |
| Tweak rate limits | RATE_LIMITS in src/lib/rate-limit.ts |