007 — WhatsApp channel via Baileys (no Cloud API fallback)
Status: Aceita (2026-06-03)
Contexto
Section titled “Contexto”- BR é whats-first. Casal abre whats 50x/dia; web app de finanças disputa atenção com tudo. WhatsApp não tem concorrente direto pra “agente do casal”.
- Group chat 1:1 entre o casal + bot é UX nativa pra DDD
Household(cenário 005). Ambos members no mesmochat_id— o canal já modela a granularidade do aggregate. - Upload de fatura PDF (cenário 009) já é hábito BR — casal manda extrato por whats hoje. Web upload é fricção; whats upload é gesto natural.
- CEO doc decisão #9: K-factor 1.5 viral depende de habit diário. WhatsApp tem; web não. Casal não volta diariamente num site novo.
- Domain (cenários 008/009/012/013) já é stateless e modal-neutro:
AgentChat.ask({messages, resourceId, threadId})comcontentmultimodal opaco (file parts via 009). Só falta adapter de canal pra plugar. - ADR 006 (anonymous-first) ancora
resourceIdem cookie/localStorage no contexto web. Whats-first muda a fonte doresourceId— não muda o contract opaco.
Decisão
Section titled “Decisão”Baileys é o único adapter de canal conversacional em produção. Sem Cloud API (Meta) como fallback, sem Telegram, sem WebSocket próprio.
Stack e layering
Section titled “Stack e layering”- Baileys (
@whiskeysockets/baileys) — multi-device, TypeScript, MIT, sem auth oficial Meta. Entra na lista adopt do ADR 001 (atualizar quando o primeiro consumer instalar). - Port
WhatsAppGatewayemsrc/contexts/agent/application/— interface limpa: receber eventos de mensagem, enviar mensagem, baixar mídia decriptada. - Adapter
BaileysAdapteremsrc/contexts/agent/infrastructure/— wrapper sobre Baileys; encapsula pair multi-device, reconexão, decrypt de mídia, store de sessão. - Test fake
MockWhatsAppGatewaycolocated emsrc/contexts/agent/infrastructure/— segue padrão deInMemoryBudgetRepository(ADR 002, gotcha “test fake é adapter legítimo”). - Port
HouseholdLookupemsrc/contexts/agent/application/— mapeiachatId → Household. Bootstrap viabootstrapFromChat({chatId, senderId, referralToken?})(entry direta whats, ADR 010); composition root resolve. - VO
WhatsAppMessageEventemsrc/contexts/agent/domain/whatsapp/— normaliza payload Baileys pro shape queAgentChat.askconsome (text + file parts + metadata).
Identidade e binding
Section titled “Identidade e binding”resourceId = Household.id.threadId = chatId(DM ou group id whats).- Onboarding cria
Householdno primeiro evento dechatIddesconhecido — chat-as-onboarding (ADR 006), versão whats. Sem signup screen. - Identidade do member via
waid(WhatsApp ID) do remetente.Member.create({name, waid})quando o agent extrair nome via conversa natural. waid mora no member como identifier externo;Member.idUUID continua sendo a identidade interna (gotcha “Member promovido VO → Entity”). - Mapping
waid ↔ Member.idfica no caller (composition root), agent continua sem conhecer waid (gotcha “resourceId opaco no contract, mapping fica no caller”).
Fluxo de mensagem
Section titled “Fluxo de mensagem”- Baileys emite evento de mensagem →
BaileysAdapternormaliza praWhatsAppMessageEvent. - Composition root resolve
chatId → Household.idviaHouseholdLookup(ou cria Household se desconhecido). - Composition root chama
AgentChat.ask({messages, resourceId: Household.id, threadId: chatId}). AgentChatretorna resposta (texto + tool-calls opcionais já executados); composition root envia viaWhatsAppGateway.send(chatId, text).- Mídia (PDF de fatura, cenário 009) baixada via
WhatsAppGateway.downloadMedia(...)antes de empilhar nopendingFilesdoAgentChat.ask(gotcha “Multimodal: extrair file bytes per-ask()”).
Web canal
Section titled “Web canal”- Web (Astro, ADR 005) entrega só: landing PT-BR, share link UX (referral attribution), blog SEO, dashboard read-only futuro.
- Sem chat UI web em produção. Landing-003 (chat embed teaser) leva pra
wa.me/<bot>?text=...em vez de/chatweb — ajustar landing-003 quando ADR 007 sair. - Chat web do
vercel/chatbotstarter (ADR 001) fica como scaffold de dev/debug interno, não canal user-facing.
Test tier
Section titled “Test tier”- Whats specs (wa-001/002/003 planejados) vivem em tier domain (ADR 002) usando
MockWhatsAppGateway. Adapter realBaileysAdapterganha spec colocada emsrc/contexts/agent/infrastructure/BaileysAdapter.spec.tssem doc Gherkin (ADR 003), gated por env porque pair multi-device exige QR scan real.
Consequências
Section titled “Consequências”Positivas
Section titled “Positivas”- Canal nativo do BR — fricção zero pra adoção, alinha com K-factor viral (CEO doc).
- Group chat 1:1 =
Householdmapeado pelo canal. Ambos members veem mesma conversa, mesmo state. UX e DDD convergem sem cerimônia. - Upload de fatura PDF (009) usa gesto natural do casal. Sem hábito novo pra criar.
- Domain de
agent/(008–015) intacto.AgentChatstateless já recebemessages/resourceId/threadIdopacos — só plugou outro caller. - Tools (read 008, write 013), memory (012), multimodal (009) — tudo reaproveitado sem mudança.
- ADR 006 (anonymous-first) cresce naturalmente:
waidsubstitui cookie UUID como entry identifier; resto do model continua. - Custo operacional zero por mensagem (vs Cloud API que cobra por conversa).
Negativas / Trade-offs
Section titled “Negativas / Trade-offs”- TOS risk aceito conscientemente: Baileys não é API oficial Meta. Risco real de ban no número do bot. Pair multi-device é instável — QR re-link periódico. Risco de bloqueio em massa se whats apertar.
- Mitigação única: pair backup + 1-2 números reserva pra rotação manual. Runbook fica em
docs/quando aparecer o primeiro incidente. Sem driver alternativo, sem auto-failover. - Cold outreach via whats proibido pelo TOS. Captura de leads passa só por landing direct-to-whats CTA (landing-001). Nada de spam pelo número do bot — descumprir = ban garantido.
- Web some como canal de chat user-facing. Landing-003 muda CTA pra deep-link whats. Aceito: web não tem habit, whats tem.
- Baileys requer processo Node sempre online segurando socket multi-device. Não é serverless-friendly como Cloud API webhooks. Hosting precisa de container/VM persistente — fricção operacional vs Cloudflare Workers da landing (ADR 005).
- Decrypt de mídia (PDF) tem custo de CPU + memória no processo do adapter. Aceitável pra volume de fatura mensal por casal.
- Pair multi-device caduca; recovery exige operador humano scanneando QR. Sem self-service de re-link pra MVP.
Não-impactos
Section titled “Não-impactos”agent/core (008–015) não muda. Tools, memory, write-tools, idempotência poroperationId(013), forecast (014), alerts (015) — todos intactos.- Specs domain existentes continuam verdes. Whats specs entram como wa-* novos, não tocam 008–015.
Alternativas rejeitadas
Section titled “Alternativas rejeitadas”- WhatsApp Cloud API oficial (Meta / WhatsApp Business Platform): cobra por conversa (após free tier mensal), exige verificação empresarial (Facebook Business Manager + documentação corporativa), latência maior pra setup inicial, template message approval pra notificações proativas. Custos crescem linearmente com escala de ativações (CEO meta 1k casais ativos em 60d → conversas crescem). Recusado por custo recorrente + setup pesado. Reavaliar só se Baileys ban-rate inviabilizar operação.
- Telegram bot: API oficial, gratuita, polida. Penetração BR ~10-15% vs whats ~98%. Adoção do casal cai dramaticamente — perde a tese “canal onde o casal já está”. Rejeitado.
- Web app principal (Next.js chat UI como canal primário): friction de “abrir mais um site”. Casal não volta diariamente. iOS Safari sem web push competente (notificação fraca = sem habit). SEO blog ainda vale como funil de waitlist (landing-003), mas chat teaser leva pro whats, não pro web. Rejeitado como canal; mantido como scaffold de dev/debug.
- iMessage / RCS: fragmentado BR (iMessage só iOS-to-iOS, RCS adoção desigual entre operadoras), sem API estável pra bot. Rejeitado.
- WebSocket próprio + PWA: reinventar mensageria, sem habit BR, push iOS fraco, install friction. Rejeitado.
Referências
Section titled “Referências”- Baileys — https://github.com/WhiskeySockets/Baileys (multi-device, TS, MIT).
- ADR 001 — stack adopt/skip (Baileys + unpdf entram; atualizar lista quando primeiro consumer instalar).
- ADR 002 — test tiers (domain tier pra whats specs com
MockWhatsAppGateway). - ADR 005 — landing stack (web fica restrita a landing/blog/dashboard read-only).
- ADR 006 — anonymous-first identity (waid substitui cookie UUID como entry identifier whats).
- Cenários 008 (chat), 009 (fatura PDF), 012 (memória persistida), 013 (write tools) — domain estável, adapter Baileys consome.
- Cenários whatsapp wa-001/002/003 (planned, sibling deste ADR — onboarding, fatura, group).
docs/CEO.mddecisão #9 — landing+whats como primeiro move (ADR 010 supersedes waitlist mechanic); K-factor 1.5 depende de habit; BR é whats-first.