Skip to content

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

  1. Conectar Resend via standard_connectors--connect (gateway, sem secret manual).
  2. Migration: campanhas_templates, campanhas, campanhas_envios, campanhas_supressao + RLS admin-only (has_role('administrador')) + WITH CHECK.
  3. Edge resend-webhook (HMAC, atualiza campanhas_envios.eventos[]).
  4. Helper _shared/email-resend.ts (envio individual via gateway, retorna provider_id).

Fase 2 — Templates

  1. Página /admin/comunicacao/templates: lista + editor (canal, assunto, conteúdo Markdown/HTML, variáveis detectadas auto).
  2. Edge admin-campanhas-crud (templates: list/create/update/delete, RLS admin).
  3. Preview com dados fake + envio de teste para email/WhatsApp do admin logado.

Fase 3 — Seletor de público

  1. Componente <PublicoSelector>: multi-select escolas + checkboxes papéis + contagem em tempo real.
  2. Edge admin-campanhas-publico retorna { total, porCanal: { email, whatsapp, semContato } }.

Fase 4 — Campanhas + agendamento

  1. Página /admin/comunicacao/campanhas: wizard (template → público → agendamento → revisar → disparar).
  2. Edge admin-campanhas-disparar: cria campanhas_envios em lotes de 100 (queryInChunks).
  3. Cron campanhas-worker (a cada 1 min): drena pendentes respeitando rate-limit, envia, grava provider_id.

Fase 5 — Métricas + supressão

  1. Dashboard métricas por campanha (cards + tabela de envios com filtro).
  2. Link de descadastro (handle-email-unsubscribe style) → grava em campanhas_supressao.
  3. Worker checa supressão antes de enfileirar.

Fase 6 — Testes + docs

  1. Vitest contract: CRUD, RLS cross-escola, rate-limit.
  2. RTL: wizard completo + preview.
  3. Doc final em docs/features/admin/COMUNICACAO_CAMPANHAS.md + ADR provedor email + sidebar VitePress.
  4. mem://features/admin/comunicacao-campanhas.md + atualização do mem://index.md.

Detalhes técnicos

  • Variáveis: &#123;&#123;nome&#125;&#125;, &#123;&#123;escola&#125;&#125;, &#123;&#123;olimpiada&#125;&#125; — render server-side em _shared/template-render.ts (sandboxed, sem eval, 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, recebe campanha_envio_id no metadata.
  • LGPD: campanhas para "responsável" exigem opt-in registrado; supressão é definitiva.
  • Logs: toda ação via registrarLog com action comunicacao.campanha.* — registrar no LOG_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.