Plano: Resend + UI Admin de Campanhas (email + WhatsApp)
Status: backlog (planejado, não iniciado) Owner: Admin OLP Última atualização: 2026-05-07
Contexto e ponto de partida
A plataforma hoje tem:
- WhatsApp: Wasender (envio real, rate-limit 6 s/msg, fila sequencial). Ver
mem://infrastructure/wasender-rate-limiting-standard. - SMS: Twilio (apenas OTP, fora de escopo).
- Email: não existe provedor. O módulo
comunicacao-escola(coord) só registra mensagens em log interno — nada sai por email.
Portanto, Resend será o primeiro provedor de email da plataforma. A nova UI de campanhas cobre email (Resend) + WhatsApp (Wasender já existente). Não há substituição — é adição.
Escopo aprovado
- Acesso: apenas Admin OLP (
/admin). - Públicos-alvo: por escola(s) + por papel (coord, prof, aluno, responsável), combináveis.
- Provedor email: Resend via connector (gateway Lovable).
- Features MVP: templates com variáveis
{{...}}, agendamento, preview + envio de teste, métricas (entregue / aberto / clicado / falha / bounce).
Fora de escopo (futuro)
- A/B testing de assunto.
- Drip campaigns / automações por evento.
- Editor visual drag-and-drop (mantém Markdown + HTML manual).
- Disparo iniciado por coord (apenas Admin OLP no MVP).
Arquitetura
text
/admin/comunicacao
├── Campanhas criar / agendar / disparar
├── Templates CRUD email + WhatsApp (variáveis)
├── Públicos seletor reutilizável (escolas × papéis)
└── Métricas por campanha + agregadas
Edge Functions
├── admin-campanhas-crud CRUD campanhas/templates (RLS admin)
├── admin-campanhas-publico resolve audiência → contagem por canal
├── admin-campanhas-disparar enfileira lote (EdgeRuntime.waitUntil)
├── campanhas-worker cron: drena fila respeitando rate-limit
└── resend-webhook delivered/opened/clicked/bounced/complained
Tabelas novas
├── campanhas_templates canal: email|whatsapp, assunto, conteúdo, variaveis[]
├── campanhas template_id, publico jsonb, agendado_para, status, totals
├── campanhas_envios campanha_id, destinatario, canal, status, provider_id, eventos[]
└── campanhas_supressao email/telefone, motivo (opt-out + bounces)Rate-limit: worker em lotes — WhatsApp 6 s/msg (MESSAGING_RATE_LIMITS), Resend 50/lote. Idempotência: idempotency_key = campanha_id:hash(destinatario). Padrões obrigatórios: ADR client naming, RLS WITH CHECK, fail-close, Zod nas edges, React Query, atomic rendering, EdgeRuntime.waitUntil.
Etapas de execução
Fase 1 — Resend + infra base
- Conectar Resend via
standard_connectors--connect(gateway, sem secret manual). - Migration:
campanhas_templates,campanhas,campanhas_envios,campanhas_supressao+ RLS admin-only (has_role('administrador')) +WITH CHECK. - Edge
resend-webhook(HMAC, atualizacampanhas_envios.eventos[]). - Helper
_shared/email-resend.ts(envio individual via gateway, retornaprovider_id).
Fase 2 — Templates
- Página
/admin/comunicacao/templates: lista + editor (canal, assunto, conteúdo Markdown/HTML, variáveis detectadas auto). - Edge
admin-campanhas-crud(templates: list/create/update/delete, RLS admin). - Preview com dados fake + envio de teste para email/WhatsApp do admin logado.
Fase 3 — Seletor de público
- Componente
<PublicoSelector>: multi-select escolas + checkboxes papéis + contagem em tempo real. - Edge
admin-campanhas-publicoretorna{ total, porCanal: { email, whatsapp, semContato } }.
Fase 4 — Campanhas + agendamento
- Página
/admin/comunicacao/campanhas: wizard (template → público → agendamento → revisar → disparar). - Edge
admin-campanhas-disparar: criacampanhas_enviosem lotes de 100 (queryInChunks). - Cron
campanhas-worker(a cada 1 min): drena pendentes respeitando rate-limit, envia, gravaprovider_id.
Fase 5 — Métricas + supressão
- Dashboard métricas por campanha (cards + tabela de envios com filtro).
- Link de descadastro (
handle-email-unsubscribestyle) → grava emcampanhas_supressao. - Worker checa supressão antes de enfileirar.
Fase 6 — Testes + docs
- Vitest contract: CRUD, RLS cross-escola, rate-limit.
- RTL: wizard completo + preview.
- Doc final em
docs/features/admin/COMUNICACAO_CAMPANHAS.md+ ADR provedor email + sidebar VitePress. mem://features/admin/comunicacao-campanhas.md+ atualização domem://index.md.
Detalhes técnicos
- Variáveis:
{{nome}},{{escola}},{{olimpiada}}— render server-side em_shared/template-render.ts(sandboxed, semeval, escape HTML por padrão). - Webhook Resend: secret
RESEND_WEBHOOK_SECRET(solicitar quando Fase 1 começar). - Domínio remetente: usar Lovable Email Domain (
notify.olp.digital) ou domínio próprio Resend — decidir antes da Fase 1. - WhatsApp: reaproveita
enviarWhatsAppComLog, recebecampanha_envio_idno metadata. - LGPD: campanhas para "responsável" exigem opt-in registrado; supressão é definitiva.
- Logs: toda ação via
registrarLogcom actioncomunicacao.campanha.*— registrar noLOG_ACTION_OPTIONS(SSOT) no mesmo PR.
Pré-requisitos antes da Fase 1
- [ ] Configurar domínio de email Lovable (botão "Set up email domain") OU confirmar uso de domínio dedicado já configurado no Resend.
- [ ] Confirmar plano Resend (free: 100/dia, 3000/mês — provavelmente insuficiente).
- [ ] Definir SLA/volume esperado (estimativa de envios/mês) para dimensionar plano.
@audit pós-entrega (cada fase)
- Backend: RLS, Zod, fail-close, error semantics 401/403,
EdgeRuntime.waitUntil. - Segurança: HMAC do webhook Resend, sanitize template render, supressão consultada antes de enfileirar.
- Frontend: React Query, atomic rendering, modal density.
- Docs: SSOT da feature + ADR + sidebar + memória atualizadas no mesmo PR.