Appearance
Regras de Campanhas
Rate limit de mensagens por cliente
Configurável por organização em campaignSettings:
| Setting | Tipo | Descrição |
|---|---|---|
messageLimitDays | number? | Janela em dias (null = desabilitado) |
messageLimitCount | number? | Máximo de mensagens nessa janela |
blockMultipleCampaigns | boolean | Se true, cliente em campanha ativa não entra em outra |
Exemplo: messageLimitDays=30 e messageLimitCount=5 → cliente não recebe mais de 5 mensagens de campanha nos últimos 30 dias.
Quando o cliente excede o limite, o job é marcado como skipped (não failed). A campanha continua para outros clientes.
Janela de contagem de resposta
replyWindowHours (padrão: 48h) — tempo após envio em que respostas do cliente contam para métricas da campanha.
Apenas a primeira resposta conta. Respostas subsequentes são ignoradas.
Atribuição de conversão
Quando um cliente faz uma compra após receber campanha, o sistema verifica:
data_da_compra >= data_do_envio
data_da_compra <= data_do_envio + attributionWindowDaysSe a condição for satisfeita, a transação é atribuída à campanha (campo isInfluenced = true, channel = 'WHATSAPP').
attributionWindowDays (padrão: 7 dias) — configurável por organização.
Apenas a mensagem de campanha mais recente para o telefone do cliente é considerada. Mensagens com status FAILED são ignoradas.
Fluxo de disparo com proteção contra duplo envio
1. updateMany atomicamente: status != 'sending' → 'sending'
└─ Se já em 'sending': falha (outra instância está processando)
2. Valida pré-condições
3. Monta audiência e enfileira jobs
4. Atualiza status: 'sending' → mantém até job completionO lock é baseado na transição atômica do status no banco — não usa Redis lock para o envio em si.
Campanhas recorrentes
O CampaignSchedulerService roda a cada 5 minutos. Para cada campanha recorrente elegível:
- Tenta adquirir lock Redis:
campaign:lock:{id}(TTL 5 min) - Se lock adquirido: registra
JobRun, avançascheduledAtpara próxima ocorrência, dispara - Se lock não adquirido: outra instância está processando, pula
Recovery no boot: Verifica JobRun com status running e startedAt mais de 10 min atrás — reprocessa (crash recovery).
Avançar scheduledAt antes de enviar
Regra crítica: O sistema avança o scheduledAt para a próxima ocorrência antes de enviar. Isso garante que, mesmo se o envio falhar ou o sistema crashar, não haverá reenvio duplicado na próxima verificação.
Verificação de rate limit antes do disparo
O frontend pode chamar GET /campaigns/:id/rate-limit-check antes de confirmar o disparo. Retorna:
json
{
"tier": "TIER_1K",
"limit": 10000,
"sentOnDay": 3500,
"committedOnDay": 2000,
"campaignSize": 8000,
"willExceed": true,
"exceedBy": 3500
}committedOnDay inclui jobs agendados de outras campanhas para o mesmo dia — não só os já enviados.
Duplicação de campanha
POST /campaigns/:id/duplicate cria cópia com:
- Status:
draft - Nome:
{original} (cópia) - Copia: canal, segmento, tags, template WhatsApp/número, conteúdo Email
- NÃO copia: datas de agendamento, status de envio, métricas
Cancelamento de campanha ativa
DELETE /campaigns/:id:
- Cancela jobs pendentes na fila
whatsapp-send(BullMQ remove porcampaignId) - Para email: anula referência de campanha nas
EmailMessage(preserva histórico de tracking) - Deleta:
CampaignContent,CampaignSchedule,WhatsappMessage
Estimativa de audiência
GET /campaigns/estimate?segmentId=...&behaviorRules=...&channel=...
Conta clientes elegíveis sem disparar:
- Aplica filtros de segmento
- Aplica filtros de comportamento
- Para WhatsApp: filtra por
phoneType = MOBILEewhatsappOptOut = false - Para Email: filtra por email existente e
emailOptOut = false
Cache: no-store (sempre recalcula, audiência muda constantemente).