Skip to content

Regras de Campanhas

Rate limit de mensagens por cliente

Configurável por organização em campaignSettings:

SettingTipoDescrição
messageLimitDaysnumber?Janela em dias (null = desabilitado)
messageLimitCountnumber?Máximo de mensagens nessa janela
blockMultipleCampaignsbooleanSe 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 + attributionWindowDays

Se 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 completion

O 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:

  1. Tenta adquirir lock Redis: campaign:lock:{id} (TTL 5 min)
  2. Se lock adquirido: registra JobRun, avança scheduledAt para próxima ocorrência, dispara
  3. 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 por campaignId)
  • 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 = MOBILE e whatsappOptOut = false
  • Para Email: filtra por email existente e emailOptOut = false

Cache: no-store (sempre recalcula, audiência muda constantemente).