Skip to main content

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

  1. Inbound webhook — Meta delivers a webhook to apps/api → signature verified with crypto.timingSafeEqual → raw envelope stored in webhook_events → Supabase notifies apps/worker → worker routes to message, status, template, or journey processor.
  2. Outbound send — Operator or automation creates a send intent → Supabase records to outbox_jobsapps/worker schedules against throughput/pair limits → Meta response logged → status webhooks update the ledger.
  3. Broadcasts — Audience snapshot created → worker fans out outbox jobs → Supabase Realtime pushes campaign progress to operator dashboard.
  4. Margaret AI — Operator triggers AI reply draft → apps/app calls /api/ai-assistant → workspace LLM provider resolved from llm_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_id derived 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.
See Multi-Tenancy & Workspace Isolation for the full security model.

Margaret AI Layer

The AI layer introduces three new runtime concerns:
  • LLM provider configs — workspace-owned records in llm_provider_configs pointing at OpenAI, Anthropic, OpenRouter, Ollama, or vLLM. API keys stored in Supabase Vault via workspace_secret_bindings.
  • AI agentsai_agents table defines persona, model, tool permissions, and PII pseudonymization settings per workspace.
  • AI threadsai_threads and ai_thread_messages maintain conversation context for Margaret replies. ai_agent_executions log tool-use runs for auditability.
The consent ledger (BORD-193) provides a durable, auditable record of every opt-in and opt-out event per contact:
  • consent_events table — append-only log of consent transitions (opt-in / opt-out / revoked). An AFTER INSERT trigger on this table denormalizes the latest state into contacts.consent_state for fast reads.
  • STOP / START keyword detectionpackages/whatsapp parses 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_state before enqueuing any outbox job. Contacts in opted_out or revoked state are silently skipped.
  • REST endpointsGET /api/v1/contacts/:id/consent returns the current state and event history; POST /api/v1/contacts/:id/consent records 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.
The palette is extensible: any module can register commands via the provider’s 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 journeys table as versioned JSON. A publish mutation creates an immutable snapshot and activates the journey for live execution by apps/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_format column — stored on message_templates to 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.run is invoked automatically by the worker.
  • Draft accept / reject APIPOST /api/v1/contacts/:id/drafts/:draftId/accept and POST /api/v1/contacts/:id/drafts/:draftId/reject let 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_files table — 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 handlerapps/api processes account_alerts webhook events from Meta and stores them in account_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_logs table — 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_sweep job — a scheduled worker job that deletes records older than the workspace’s retention window. Runs daily; logs sweep metrics to audit_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_keys with 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 /welcome on first login. The wizard is also accessible from Settings.

Design System Primitives

New design-system primitives shipped in packages/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).
  • SkeletonSkeleton component 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 faviconfavicon.ico updated 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.
See Security Overview for the full control implementation status.

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.
  • 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 or className='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
The dark near-black custom sidebar theme from before PR #270 has been removed. The sidebar now inherits from the workspace’s standard light design-system tokens.
  • 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, and apps/docs to Vercel/Mintlify. Deploy apps/worker to Railway. All surfaces share the same Supabase project.
  • CI validates builds, migrations, docs lint, and secret scans. See CI/CD.