# Disparo em massa

`POST /v1/dispatches/mass` (`dispatches.create`) envia a mesma mensagem para até 30 mil contatos numa chamada, em um
ou mais canais. Exige `--lgpd`; em produção, `--confirmado` depois do "sim" do usuário. Use `--idempotencia` com um
valor estável (ex.: o nome da campanha + a data) para que uma nova tentativa não crie um segundo disparo.

```json
{
  "campaignName": "Prospecção outubro",
  "channels": ["whatsapp", "sms"],
  "message": "Olá {nome}, preparamos uma proposta exclusiva para a sua empresa.",
  "contacts": [
    { "phone": "5511987654321", "name": "Carlos Silva" },
    { "phone": "5521912345678", "name": "Marina Costa" }
  ]
}
```

| Campo | Regra |
| --- | --- |
| `channels` | Um ou mais de `whatsapp`, `sms`, `email`, `voice`. WhatsApp Oficial e vídeo não entram no disparo em massa. |
| `message` | Obrigatório, até 1.000 caracteres. `{nome}` (ou `{name}`) vira o primeiro nome do contato. No SMS, até 10 SMS. |
| `contacts` | De 1 a 30.000. Cada um com `phone`, `email` e/ou `name` (até 120). Canal de telefone pede `phone`; e-mail pede `email`. |
| `campaignName` | Opcional (até 120). Também é o assunto do e-mail quando não há `subject`. |
| `subject` | Assunto do e-mail (até 150). |
| `emailLegitimateInterestAck` | Obrigatório com o canal `email`: a versão em vigor da declaração do legítimo interesse (o erro `EMAIL_LEGITIMATE_INTEREST_REQUIRED` mostra o valor). Só com a confirmação do usuário de que a base legal é essa. |

Não há agendamento por data: o disparo começa na hora. Cada contato × canal sai a cada ~4 s, e a uma abordagem por
dia, a lista de bloqueio e o saldo são conferidos item a item.

## Antes de disparar

1. Teste com a chave de sandbox: a resposta traz `dryRun: true`, `contacts`, `channels` e `creditsRequired` (o custo
   por carteira), sem enviar nem cobrar.
2. Confira o saldo (`GET /v1/account/credits`). Faltando, a criação dá 402 `CREDITOS_INSUFICIENTES` com `missing`.
3. Mostre ao usuário: empresa e CNPJ, canais, quantidade de contatos, o texto e `creditsRequired`. Com o "sim",
   repita em produção com `--confirmado`.

Aceito: `202` com `dispatchId`, `status`, `total`, `creditsRequired`, `statusUrl` e `resultsUrl`.

## Janela e limites

- Criado fora da janela de envio: 409 `outside_send_window`, com a próxima abertura.
- A janela fechou no meio: o disparo fica `waiting` com `resumeAt` e continua do mesmo ponto quando abre.
- No máximo 3 disparos ativos por conta (`queued`, `running`, `waiting`); o 4º recebe 429 `too_many_active_dispatches`.

## Acompanhar e parar

- `GET /v1/dispatches/{dispatchId}` (`dispatches.get`): `status` (`queued`, `running`, `waiting`, `done`, `stopped`),
  `total`, `processed`, `counts` (`sent`, `failed`, `blocked`, `no_credits`, `skipped`) e os 50 últimos em `recent`.
- `GET /v1/dispatches/{dispatchId}/results?page=1` (`dispatches.results`): o desfecho de cada contato × canal, 50 por
  página, na ordem de envio — `sent`, `failed`, `blocked`, `no_credits`, `skipped` (ou `unknown`), com o motivo.
- `GET /v1/dispatches?limit=20` (`dispatches.list`, até 50): os últimos disparos — recupera o `dispatchId` depois de um
  tempo esgotado.
- `DELETE /v1/dispatches/{dispatchId}` (`dispatches.cancel`): para antes do próximo envio (`stopRequested: true`). O
  que já saiu não volta. Em produção, também com `--confirmado`.

Consulte o andamento sem pressa (a cada 30–60 s) ou use o webhook de eventos.
