Platform Architecture
Switchbord is deployed as a multi-tenant Next.js application on Vercel, backed by Supabase for persistence, auth, and realtime, with a Railway-hosted worker for background processing. There is no separate control-plane service — that architectural path was retired and all runtime responsibilities consolidated.Topology
apps/app: operator product — inbox, contacts, templates, campaigns, Margaret AI, settings. Deployed to Vercel.apps/web: marketing site, QR/opt-in flows, gated landing pages. Deployed to Vercel.apps/api: Next.js API surface for Meta webhook ingress, public REST API v1 (OpenAPI spec at/spec), compatibility endpoints, and health/status routes. Deployed to Vercel.apps/docs: Mintlify-based documentation. Deployed to Mintlify.apps/worker: queue processing, retries, campaign schedulers, message dispatch, replay tooling. Deployed to Railway.- Supabase: Postgres, Auth, Storage, Realtime, Vault. Single source of truth.
The
apps/control-plane service has been removed. All webhook-oriented control-plane work now runs inside apps/api and apps/worker. Any local references to port 4000 or apps/control-plane/.env.example are legacy artifacts.Core Runtime Paths
- Inbound webhook — Meta delivers a webhook to
apps/api→ signature verified withcrypto.timingSafeEqual→ raw envelope stored inwebhook_events→ Supabase notifiesapps/worker→ worker routes to message, status, template, or journey processor. - Outbound send — Operator or automation creates a send intent → Supabase records to
outbox_jobs→apps/workerschedules against throughput/pair limits → Meta response logged → status webhooks update the ledger. - Broadcasts — Audience snapshot created → worker fans out outbox jobs → Supabase Realtime pushes campaign progress to operator dashboard.
- Margaret AI — Operator triggers AI reply draft →
apps/appcalls/api/ai-assistant→ workspace LLM provider resolved fromllm_provider_configs→ PII pseudonymized before any prompt → response de-pseudonymized → draft surfaced in inbox. Agent execution (BORD-140–143) adds tool-use on top.
Multi-Tenant Workspace Model
Every tenant is an isolated workspace. The core isolation guarantees:- Workspace context is resolved server-side from
workspace_members— never from env vars or client-supplied headers. - All data queries are scoped by
workspace_idderived from that resolution. - Message dispatch reads Meta credentials from the owning workspace’s Vault secrets — no cross-workspace credential leakage.
- Four RBAC roles enforced at every route: owner, admin, developer, operator.
Margaret AI Layer
The AI layer introduces three new runtime concerns:- LLM provider configs — workspace-owned records in
llm_provider_configspointing at OpenAI, Anthropic, OpenRouter, Ollama, or vLLM. API keys stored in Supabase Vault viaworkspace_secret_bindings. - AI agents —
ai_agentstable defines persona, model, tool permissions, and PII pseudonymization settings per workspace. - AI threads —
ai_threadsandai_thread_messagesmaintain conversation context for Margaret replies.ai_agent_executionslog tool-use runs for auditability.
Consent Ledger
The consent ledger (BORD-193) provides a durable, auditable record of every opt-in and opt-out event per contact:consent_eventstable — append-only log of consent transitions (opt-in / opt-out / revoked). AnAFTER INSERTtrigger on this table denormalizes the latest state intocontacts.consent_statefor fast reads.- STOP / START keyword detection —
packages/whatsappparses inbound messages for localized opt-out (STOP,BLOQUEAR,FERMA,PARAR) and opt-in (START,INICIO,INIZIO,COMEÇAR) keywords in English, Italian, Portuguese, and Spanish. - Campaign dispatch opt-out gate — the broadcast engine checks
contacts.consent_statebefore enqueuing any outbox job. Contacts inopted_outorrevokedstate are silently skipped. - REST endpoints —
GET /api/v1/contacts/:id/consentreturns the current state and event history;POST /api/v1/contacts/:id/consentrecords a manual consent transition (owner/admin only).
Command Palette
The command palette (BORD-204) is a ⌘K surface for navigation, search, and actions across the operator product:command-palette.tsx— the palette shell, triggered by ⌘K / Ctrl+K. Renders a searchable list of commands grouped by category.command-palette-provider.tsx— React context that aggregates available commands from each module (inbox, contacts, campaigns, templates, journeys, settings) and manages open/close state.command-palette-hint.tsx— inline hint badge rendered in the sidebar and page headers to discover the shortcut.
registerCommands hook.
Graphical Journey Editor
A React Flow canvas at/journeys/[id] for building multi-step automations visually:
- 9 node types — Trigger, Condition, Wait, Send Template, Update Attribute, Add/Remove Tag, Outbound Webhook, AI Agent Step, and Exit.
- Validation — the editor validates required fields, edge connectivity, and cycle detection before publish. Errors surface inline on each node.
- Publish pipeline — validated graphs are serialized to the
journeystable as versioned JSON. Apublishmutation creates an immutable snapshot and activates the journey for live execution byapps/worker.
Visual Template Builder
A 3-pane builder at/templates/new for composing WhatsApp message templates:
- 7 editors — Header, Body, Footer, Buttons, Examples, Language, and Category.
parameter_formatcolumn — stored onmessage_templatesto declare the expected type and order of template variables.- Submit-to-Meta pipeline — the builder POSTs the composed template to the Meta Business API and tracks approval status via template status webhooks.
- Phone-frame renderer — a live preview component renders the template as it would appear on a device, with placeholder substitution for variables.
Auto-Trigger Agent
The auto-trigger agent (BORD-192) extends Margaret AI to draft replies automatically on inbound WhatsApp messages:- Agent run trigger — when an inbound message arrives and the workspace has an auto-trigger agent configured,
agent.runis invoked automatically by the worker. - Draft accept / reject API —
POST /api/v1/contacts/:id/drafts/:draftId/acceptandPOST /api/v1/contacts/:id/drafts/:draftId/rejectlet operators approve or discard AI-generated drafts. - Draft banner in inbox — a prominent banner in the conversation view alerts operators when a draft is pending, with one-click accept/reject actions.
Asset Storage
Asset storage (BORD-187) provides workspace-scoped file uploads for templates, journeys, and knowledge assets:asset_filestable — records file metadata (name, MIME type, size, workspace scope) and points to the stored object.- S3-compatible pipeline — uploads flow through a presigned-URL pipeline to any S3-compatible object store. The worker retrieves objects at dispatch time for media sends.
- Workspace isolation — every file is scoped by
workspace_id; cross-workspace access is rejected at the API layer.
Account Alerts
Account alerts (BORD-189) surface Meta account-level issues to operators:- Webhook handler —
apps/apiprocessesaccount_alertswebhook events from Meta and stores them inaccount_alert_events. - Operator UI panel — a dedicated panel in settings displays active alerts (e.g. policy violations, rate limit warnings, credential expiry) with severity and resolution steps.
Audit Logs
Audit logs (BORD-158) record operator actions for compliance and forensics:audit_logstable — workspace-scoped, append-only log of actor, action, target resource, and diff payload.writeAuditLog()— a server-side helper that every mutation route calls before committing. Guarantees consistent structure and prevents accidental omission.
GDPR Data Retention
GDPR data retention (BORD-165 / BORD-167) enforces per-workspace retention limits:data_retention_days— a workspace-level setting (default 365). Controls how long webhook envelopes, message content, and consent events are retained.retention_sweepjob — a scheduled worker job that deletes records older than the workspace’s retention window. Runs daily; logs sweep metrics toaudit_logs.
API Key Generation
API key generation (BORD-178) allows workspace owners to provision long-lived API keys:swb_-prefixed keys — generated server-side with a cryptographically random 32-byte secret. The prefix enables fast visual identification and programmatic detection in logs.- Key metadata — stored in
api_keyswith creation timestamp, last-used timestamp, and optional description. Keys are hashed at rest; the full secret is shown only once at creation.
Guided Wizard
The guided wizard (BORD-194) provides a first-run-to-first-message onboarding flow:- Step sequence — connect WhatsApp number → configure LLM provider → create first template → send first message.
- Progress tracking — stored in
workspace_onboarding_state. The wizard auto-advances and is dismissible after completion. - Entry point — newly created workspaces redirect to
/welcomeon first login. The wizard is also accessible from Settings.
Design System Primitives
New design-system primitives shipped inpackages/design-system:
- Design tokens — semantic color, spacing, and typography tokens in
tokens.css, consumed by all shadcn/ui components. - Motion — consistent transition and animation utilities in
motion.ts(durations, easings, enter/exit presets). - Skeleton —
Skeletoncomponent with pulse animation, used as loading placeholders across the operator product.
Brand Updates
- Transparent wordmark — the expanded sidebar logo now uses a transparent-background SVG wordmark for clean rendering on any surface.
- Black-background favicon —
favicon.icoupdated to a black-background variant for high visibility in browser tabs.
Data & Security Principles
- Every webhook envelope is immutable; raw body + metadata retained with configurable retention controls.
- Delivery history is append-only (
outbox_jobs,message_status_events) — never overwritten. - RBAC, feature flags, and provider secrets remain server-side only.
- Private Supabase Realtime Broadcast topics power operator realtime feeds; no Postgres Changes broadcast to avoid scaling issues.
- Audit log on every operator action and credential rotation.
- Consent ledger enforces opt-out before any campaign or automation dispatch.
- Data retention sweeps enforce GDPR deletion timelines per workspace.
Operator UI — Sidebar & Navigation
The operator UI (apps/app) uses a sidebar built on shadcn/ui’s Sidebar primitives.
Component
- File:
apps/app/components/operator-sidebar.tsx(~165 lines) - Pattern:
collapsible='icon'— collapses to an icon-only rail; expands on hover or toggle. Props interface is stable. - Layout: Each page that needs the sidebar wraps its content with
SidebarProvider.
Navigation structure
- Top section (flat nav): Inbox / Contacts / Broadcasts / Templates / Operations
- Bottom section (footer): Settings / Docs / User account
Theming
The sidebar uses standard shadcn light CSS variable defaults — there is no custom dark theme orclassName='dark' applied to the sidebar element.
Relevant variables (defined under :root in packages/design-system/styles/globals.css):
--sidebar: near-white background--sidebar-foreground: near-black text
Logo
- Expanded: full wordmark
- Collapsed: icon-only mark
Monitoring + Deployment
- Health endpoints:
/health,/ready,/internal/runtime. - Operational endpoints:
/internal/webhooks,/internal/webhook-rejections,/internal/webhooks/:id/replay. - OpenAPI spec + Scalar explorer:
/spec. - Deploy
apps/app,apps/web,apps/api, andapps/docsto Vercel/Mintlify. Deployapps/workerto Railway. All surfaces share the same Supabase project. - CI validates builds, migrations, docs lint, and secret scans. See CI/CD.