Architettura della Piattaforma
Switchbord è distribuito come applicazione Next.js multi-tenant su Vercel, supportata da Supabase per persistenza, autenticazione e realtime, con un worker ospitato su Railway per l’elaborazione in background. Non esiste un servizio control-plane separato — quel percorso architetturale è stato ritirato e tutte le responsabilità di runtime sono state consolidate.Topologia
apps/app: prodotto per l’operatore — inbox, contatti, template, campagne, Margaret AI, impostazioni. Distribuito su Vercel.apps/web: sito marketing, flussi QR/opt-in, landing page con accesso controllato. Distribuito su Vercel.apps/api: superficie API Next.js per l’ingress dei webhook Meta, API REST pubblica v1 (specifica OpenAPI su/spec), endpoint di compatibilità e route di health/status. Distribuito su Vercel.apps/docs: documentazione basata su Mintlify. Distribuito su Mintlify.apps/worker: elaborazione delle code, retry, scheduler delle campagne, dispatch dei messaggi, strumenti di replay. Distribuito su Railway.- Supabase: Postgres, Auth, Storage, Realtime, Vault. Fonte unica di verità.
Il servizio
apps/control-plane è stato rimosso. Tutto il lavoro control-plane orientato ai webhook ora viene eseguito all’interno di apps/api e apps/worker. Eventuali riferimenti locali alla porta 4000 o a apps/control-plane/.env.example sono artefatti legacy.Percorsi Core del Runtime
- Webhook in entrata — Meta invia un webhook a
apps/api→ firma verificata concrypto.timingSafeEqual→ busta raw memorizzata inwebhook_events→ Supabase notificaapps/worker→ il worker instrada verso il processore di messaggi, stato, template o journey. - Invio in uscita — L’operatore o un’automazione crea un intent di invio → Supabase registra su
outbox_jobs→apps/workerpianifica in base ai limiti di throughput/coppia → la risposta di Meta viene registrata → i webhook di stato aggiornano il ledger. - Broadcast — Viene creato uno snapshot dell’audience → il worker distribuisce gli outbox job → Supabase Realtime spinge l’avanzamento della campagna alla dashboard dell’operatore.
- Margaret AI — L’operatore attiva la bozza di risposta AI →
apps/appchiama/api/ai-assistant→ il provider LLM del workspace viene risolto dallm_provider_configs→ i dati PII vengono pseudonimizzati prima di qualsiasi prompt → la risposta viene de-pseudonimizzata → la bozza viene mostrata nell’inbox. L’esecuzione tramite agent (BORD-140–143) aggiunge l’uso di strumenti (tool-use) al di sopra.
Modello di Workspace Multi-Tenant
Ogni tenant è un workspace isolato. Le garanzie di isolamento fondamentali:- Il contesto del workspace viene risolto lato server da
workspace_members— non da variabili d’ambiente o header forniti dal client. - Tutte le query sui dati sono ambitate (scoped) da
workspace_idderivato da tale risoluzione. - Il dispatch dei messaggi legge le credenziali Meta dai segreti Vault del workspace proprietario — nessuna fuoriuscita di credenziali tra workspace.
- Quattro ruoli RBAC applicati su ogni route: owner, admin, developer, operator.
Livello Margaret AI
Il livello AI introduce tre nuove preoccupazioni di runtime:- Configurazioni provider LLM — record posseduti dal workspace in
llm_provider_configsche puntano a OpenAI, Anthropic, OpenRouter, Ollama o vLLM. Le chiavi API sono memorizzate in Supabase Vault tramiteworkspace_secret_bindings. - Agent AI — la tabella
ai_agentsdefinisce persona, modello, permessi degli strumenti e impostazioni di pseudonimizzazione PII per workspace. - Thread AI —
ai_threadseai_thread_messagesmantengono il contesto di conversazione per le risposte di Margaret.ai_agent_executionsregistra le esecuzioni di tool-use per la tracciabilità (auditability).
Registro dei Consensi (Consent Ledger)
Il registro dei consensi (BORD-193) fornisce un record durevole e verificabile di ogni evento di opt-in e opt-out per contatto:- Tabella
consent_events— log append-only delle transizioni di consenso (opt-in / opt-out / revocato). Un triggerAFTER INSERTsu questa tabella denormalizza lo stato più recente incontacts.consent_stateper letture rapide. - Rilevamento delle parole chiave STOP / START —
packages/whatsappanalizza i messaggi in entrata per le parole chiave localizzate di opt-out (STOP,BLOQUEAR,FERMA,PARAR) e opt-in (START,INICIO,INIZIO,COMEÇAR) in inglese, italiano, portoghese e spagnolo. - Gate opt-out del dispatch di campagna — il motore di broadcast verifica
contacts.consent_stateprima di accodare qualsiasi outbox job. I contatti in statoopted_outorevokedvengono silenziosamente saltati. - Endpoint REST —
GET /api/v1/contacts/:id/consentrestituisce lo stato attuale e la cronologia degli eventi;POST /api/v1/contacts/:id/consentregistra una transizione di consenso manuale (solo owner/admin).
Command Palette
La command palette (BORD-204) è una superficie ⌘K per la navigazione, la ricerca e le azioni nel prodotto per l’operatore:command-palette.tsx— il contenitore della palette, attivato da ⌘K / Ctrl+K. Renderizza un elenco di comandi ricercabile raggruppato per categoria.command-palette-provider.tsx— contesto React che aggrega i comandi disponibili da ogni modulo (inbox, contatti, campagne, template, journey, impostazioni) e gestisce lo stato apertura/chiusura.command-palette-hint.tsx— badge di suggerimento inline mostrato nella sidebar e nelle intestazioni di pagina per scoprire la scorciatoia.
registerCommands del provider.
Editor Visivo dei Journey
Un canvas React Flow su/journeys/[id] per costruire automazioni multi-step in modo visivo:
- 9 tipi di nodo — Trigger, Condition, Wait, Send Template, Update Attribute, Add/Remove Tag, Outbound Webhook, AI Agent Step, e Exit.
- Validazione — l’editor valida i campi obbligatori, la connettività degli edge e la rilevazione dei cicli prima della pubblicazione. Gli errori vengono mostrati inline su ogni nodo.
- Pipeline di pubblicazione — i grafi validati vengono serializzati nella tabella
journeyscome JSON versionato. Una mutationpublishcrea uno snapshot immutabile e attiva il journey per l’esecuzione live da parte diapps/worker.
Costruttore Visivo di Template
Un builder a 3 pannelli su/templates/new per comporre template di messaggi WhatsApp:
- 7 editor — Header, Body, Footer, Buttons, Examples, Language e Category.
- Colonna
parameter_format— memorizzata sumessage_templatesper dichiarare il tipo e l’ordine previsti delle variabili del template. - Pipeline di invio a Meta — il builder invia (POST) il template composto alla Meta Business API e traccia lo stato di approvazione tramite i webhook di stato dei template.
- Renderer a cornice telefono — un componente di anteprima live rende il template come apparirebbe su un dispositivo, con sostituzione dei placeholder per le variabili.
Agent con Auto-Trigger
L’agent con auto-trigger (BORD-192) estende Margaret AI per redigere automaticamente risposte sui messaggi WhatsApp in entrata:- Trigger di esecuzione dell’agent — quando arriva un messaggio in entrata e il workspace ha configurato un agent con auto-trigger,
agent.runviene invocato automaticamente dal worker. - API di accettazione/rifiuto della bozza —
POST /api/v1/contacts/:id/drafts/:draftId/acceptePOST /api/v1/contacts/:id/drafts/:draftId/rejectpermettono agli operatori di approvare o scartare le bozze generate dall’AI. - Banner della bozza nell’inbox — un banner prominente nella vista della conversazione avvisa gli operatori quando una bozza è in attesa, con azioni di accettazione/rifiuto a un clic.
Storage degli Asset
Lo storage degli asset (BORD-187) fornisce upload di file ambitati per workspace per template, journey e asset di knowledge base:- Tabella
asset_files— registra i metadati del file (nome, tipo MIME, dimensione, ambito workspace) e punta all’oggetto memorizzato. - Pipeline compatibile con S3 — gli upload passano attraverso una pipeline con URL prefirmato verso qualsiasi object store compatibile con S3. Il worker recupera gli oggetti al momento del dispatch per gli invii di media.
- Isolamento del workspace — ogni file è ambitato da
workspace_id; l’accesso cross-workspace viene rifiutato a livello di API.
Alert sull’Account
Gli alert sull’account (BORD-189) mostrano agli operatori i problemi a livello di account Meta:- Handler del webhook —
apps/apielabora gli eventi webhookaccount_alertsdi Meta e li memorizza inaccount_alert_events. - Pannello UI per l’operatore — un pannello dedicato nelle impostazioni mostra gli alert attivi (ad es. violazioni delle policy, avvisi sui rate limit, scadenza delle credenziali) con gravità e passaggi di risoluzione.
Log di Audit
I log di audit (BORD-158) registrano le azioni degli operatori per conformità e analisi forense:- Tabella
audit_logs— log append-only ambitato per workspace di attore, azione, risorsa di destinazione e payload diff. writeAuditLog()— un helper lato server che ogni route di mutazione chiama prima di eseguire il commit. Garantisce una struttura coerente e previene omissioni accidentali.
Conservazione dei Dati GDPR
La conservazione dei dati GDPR (BORD-165 / BORD-167) applica limiti di conservazione per workspace:data_retention_days— un’impostazione a livello di workspace (default 365). Controlla per quanto tempo vengono conservate le buste dei webhook, il contenuto dei messaggi e gli eventi di consenso.- Job
retention_sweep— un job del worker pianificato che elimina i record più vecchi della finestra di conservazione del workspace. Viene eseguito giornalmente; registra le metriche di sweep inaudit_logs.
Generazione delle Chiavi API
La generazione delle chiavi API (BORD-178) consente ai proprietari del workspace di fornire chiavi API di lunga durata:- Chiavi con prefisso
swb_— generate lato server con un segreto casuale crittograficamente sicuro di 32 byte. Il prefisso consente un’identificazione visiva rapida e il rilevamento programmatico nei log. - Metadati della chiave — memorizzati in
api_keyscon timestamp di creazione, timestamp di ultimo utilizzo e descrizione opzionale. Le chiavi sono sottoposte a hash a riposo; il segreto completo viene mostrato solo una volta alla creazione.
Wizard Guidato
Il wizard guidato (BORD-194) fornisce un flusso di onboarding dal primo avvio al primo messaggio:- Sequenza di step — collega il numero WhatsApp → configura il provider LLM → crea il primo template → invia il primo messaggio.
- Tracciamento dell’avanzamento — memorizzato in
workspace_onboarding_state. Il wizard avanza automaticamente ed è ignorabile dopo il completamento. - Punto di ingresso — i workspace appena creati vengono reindirizzati a
/welcomeal primo login. Il wizard è anche accessibile dalle Impostazioni.
Primitive del Design System
Nuove primitive del design system rilasciate inpackages/design-system:
- Design token — token semantici di colore, spaziatura e tipografia in
tokens.css, utilizzati da tutti i componenti shadcn/ui. - Motion — utility di transizione e animazione coerenti in
motion.ts(durate, easing, preset di enter/exit). - Skeleton — componente
Skeletoncon animazione pulse, utilizzato come placeholder di caricamento in tutto il prodotto per l’operatore.
Aggiornamenti al Brand
- Wordmark trasparente — il logo della sidebar espansa ora utilizza un wordmark SVG con sfondo trasparente per un rendering pulito su qualsiasi superficie.
- Favicon con sfondo nero —
favicon.icoaggiornato a una variante con sfondo nero per una maggiore visibilità nelle tab del browser.
Principi di Dati e Sicurezza
- Ogni busta di webhook è immutabile; il corpo raw + i metadati vengono conservati con controlli di conservazione configurabili.
- La cronologia di consegna è append-only (
outbox_jobs,message_status_events) — non viene mai sovrascritta. - RBAC, feature flag e segreti del provider rimangono solo lato server.
- Topic Broadcast privati di Supabase Realtime alimentano i feed realtime dell’operatore; nessun broadcast di Postgres Changes per evitare problemi di scalabilità.
- Log di audit su ogni azione dell’operatore e rotazione delle credenziali.
- Il registro dei consensi applica l’opt-out prima di qualsiasi dispatch di campagna o automazione.
- Gli sweep di conservazione dei dati applicano le tempistiche di eliminazione GDPR per workspace.
UI Operatore — Sidebar e Navigazione
L’UI dell’operatore (apps/app) utilizza una sidebar costruita sulle primitive Sidebar di shadcn/ui.
Componente
- File:
apps/app/components/operator-sidebar.tsx(~165 righe) - Pattern:
collapsible='icon'— si collassa in una barra solo icone; si espande al passaggio del mouse o al toggle. L’interfaccia delle prop è stabile. - Layout: Ogni pagina che necessita della sidebar avvolge il proprio contenuto con
SidebarProvider.
Struttura di navigazione
- Sezione superiore (nav piatta): Inbox / Contatti / Broadcast / Template / Operazioni
- Sezione inferiore (footer): Impostazioni / Documentazione / Account utente
Theming
La sidebar utilizza i valori predefiniti standard delle variabili CSS chiare di shadcn — non c’è un tema scuro personalizzato néclassName='dark' applicato all’elemento sidebar.
Variabili rilevanti (definite sotto :root in packages/design-system/styles/globals.css):
--sidebar: sfondo quasi bianco--sidebar-foreground: testo quasi nero
Logo
- Espanso: wordmark completo
- Collassato: solo il marchio icona
Monitoraggio + Deployment
- Endpoint di health:
/health,/ready,/internal/runtime. - Endpoint operativi:
/internal/webhooks,/internal/webhook-rejections,/internal/webhooks/:id/replay. - Specifica OpenAPI + explorer Scalar:
/spec. - Distribuisci
apps/app,apps/web,apps/apieapps/docssu Vercel/Mintlify. Distribuisciapps/workersu Railway. Tutte le superfici condividono lo stesso progetto Supabase. - La CI valida build, migrazioni, lint della documentazione e scansioni dei segreti. Vedi CI/CD.