> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Campanhas e templates

> Um público que é sempre um segmento, um gatilho, uma sequência e dois limites — e templates recusados no salvamento em vez de às três da manhã.

Uma campanha são quatro coisas: **um público**, **um gatilho**, **uma sequência**
de uma ou mais mensagens e **um limite** de com que frequência ela pode alcançar a
mesma pessoa. As palavras moram num template, e é o template que decide a categoria
que quem recebe pode desligar.

## As palavras são um template

```bash theme={null}
curl -X POST "https://api.userkit.dev/v1/organization/message-templates?environment=live" \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: org_4b1e…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Win-back — primeiro aviso",
    "channel": "email",
    "category": "marketing",
    "subject": "{{if .Contact.Name}}{{.Contact.Name}}, {{end}}sua conta continua aqui",
    "body": "Guardamos tudo do seu {{.Plan.Name}} exatamente como você deixou."
  }'
```

**O assunto e o corpo são compilados no salvamento, e um template que não renderiza
é recusado em vez de gravado.** `{{.Contact.Nickname}}` responde
`400 template_refused` nomeando o campo e listando o que existe no lugar — nunca
uma linha que renderiza `<no value>` em dez mil caixas de entrada às três da manhã. Um
template quebrado tem que falhar onde alguém está olhando.

### O contexto é fechado

Essa checagem só é possível porque o que um template pode dizer é uma lista finita:

| Referência                                               | É                                                                                                                         |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `.Contact.Name`, `.Contact.Email`, `.Contact.ExternalID` | Quem recebe. `Name` é vazio quando ninguém nos disse; `ExternalID` é vazio para um contato que o seu backend nunca nomeou |
| `.Contact.Attr "chave"`                                  | Qualquer coisa passada no `identify`                                                                                      |
| `.Customer.Name`                                         | O time em que a pessoa estava, vazio quando não há                                                                        |
| `.Plan.Name`                                             | O plano, como os entitlements o resolvem: uma chave de plano, `none`, ou `unknown` quando a resolução falhou              |
| `.Entitlements.Has "feature_key"`                        | Se aquele plano inclui um recurso                                                                                         |
| `.Billing.InvoiceURL`, `.Billing.GraceUntil`             | O que este time deve agora, vazio para todo mundo sem fatura em aberto                                                    |
| `.Data.nome_da_variavel`                                 | Um valor que **quem envia** manda, e só existe se o template tiver declarado essa variável                                |

Nada além disso. Sem ambiente, sem organização, sem requisição, sem relógio, sem
consulta — e isso é uma propriedade de segurança antes de ser de ergonomia: o que um
template alcança é alcançável por qualquer um que possa escrever um.

Um **atributo** e uma **chave de recurso** são lidos por chamada e não por índice, e
a diferença é o modo de falha. Os dois são dados, então as chaves válidas não são
conhecíveis no salvamento; uma chamada responde `""` ou `false` para uma chave que
ninguém definiu, enquanto um índice renderizaria `<no value>` na caixa de alguém.

### As variáveis são declaradas

`.Data` é a única coisa aberta num contexto cujo desenho inteiro é ser fechado, e
ela é aberta numa direção só: o template **declara** quais chaves lê.

```json theme={null}
{
  "name": "Fatura vencendo",
  "channel": "in_app",
  "category": "transactional",
  "key": "fatura_vencendo",
  "subject": "Sua fatura {{.Data.invoice_id}} vence {{.Data.vencimento}}",
  "body": "São {{.Data.valor}}.",
  "variables": ["invoice_id", "vencimento", "valor"]
}
```

`{{.Data.invoice_id}}` só compila se `invoice_id` estiver em `variables` — é isso
que mantém a checagem do salvamento possível com um valor aberto. E a mesma lista é
o contrato do envio: quem chama
[`/v1/notifications`](/pt-br/guides/notifications) sem um dos valores declarados
recebe `400 missing_template_data` com o nome do que falta.

A `key` é como um programa endereça o template — ela sobrevive ao dia em que
alguém renomeia, que é o que um nome não faz. A mesma chave existe uma vez por
canal, e é isso que faz uma chamada alcançar `in_app`, `email` e `whatsapp`.

### Uma lista, quando a mensagem tem linhas

Um relatório semanal precisa de N linhas, e uma variável simples não dá conta.
Declare uma **lista com as colunas** e o corpo percorre:

```json theme={null}
{
  "name": "Relatório semanal",
  "channel": "email",
  "category": "transactional",
  "key": "relatorio_semanal",
  "subject": "Seu relatório da semana {{.Data.semana}}",
  "body": "Os engajados:\n\n{{range .Data.membros}}• {{.nome}} — {{.pontos}} pontos\n{{end}}",
  "variables": ["semana", { "name": "membros", "fields": ["nome", "pontos"] }]
}
```

```json theme={null}
{
  "template": "relatorio_semanal",
  "data": {
    "semana": "12",
    "membros": [
      { "nome": "Ana", "pontos": "92" },
      { "nome": "João", "pontos": "41" }
    ]
  }
}
```

**`range` só percorre uma lista declarada**, e as colunas são a razão. O laço
reassocia o `.`, e a checagem do salvamento vale exatamente o que ela souber
sobre o que `.` é em cada ponto — as colunas são de onde vem esse
conhecimento. Um `{{.email}}` numa lista que só declarou `nome` e `pontos` é
recusado ao salvar, com o nome da coluna que não existe.

O que isso compra é onde a formatação mora. A alternativa é montar a tabela no
seu próprio deploy e mandar como uma string só — funciona, e devolve a ordem
das colunas, o cabeçalho e o "corta em três linhas" para dentro do código. Com
a lista, o template desenha a linha e você manda as células.

Duas coisas que valem saber antes:

* **Uma linha sem uma coluna declarada é `400`**, nomeando-a uma vez
  (`membros[0].pontos`) e não uma vez por linha.
* **`{{else}}`** cobre a lista vazia: `{{range .Data.membros}}…{{else}}Ninguém
  esta semana.{{end}}`, sem precisar de um segundo template.
* Os valores são **strings**, inclusive dentro das linhas. Um número formatado
  por nós é um número formatado pelas nossas regras na moeda de outra pessoa.

### Uma edição vira uma versão

Editar as palavras escreve uma **versão nova**; a anterior continua guardada, e
`PATCH { "version": 2 }` traz de volta sem apagar a mais recente.
`GET /v1/organization/message-templates/{id}/versions` lista o histórico com quem
escreveu cada uma.

Renomear ou arquivar não escreve versão — só as palavras escrevem. É o que torna
seguro editar esse texto pelo painel, pela API e pelo
[MCP](/pt-br/guides/mcp): o pior resultado de uma edição infeliz é um `restore`.

Texto, cadeias de campos, `{{if}}`/`{{else}}` e as funções padrão funcionam.
**`range`, `with`, `template` e variáveis são recusados** — não por serem perigosos,
mas porque reassociam o `.`, e a checagem que recusa um campo inexistente depende de
saber o que `.` é em cada ponto. Nada no contexto é uma coleção, então não há o que
percorrer.

### A categoria mora aqui

Uma categoria é uma afirmação sobre o que a mensagem **diz**, e o template é o único
objeto que sabe o que ela diz. A campanha herda em vez de escolher, que é o que
impede o mesmo texto de win-back de sair como `product_news` na terça. Veja
[mensagens e consentimento](/pt-br/guides/messages) para o que cada uma significa.

`transactional` é a exceção e tem regra própria: é o recibo do qual ninguém se
descadastra, então ele **precisa de uma `key`** e **não pode ser passo de
campanha** — uma sequência construída sobre um alcançaria uma audiência que não
tem como pará-la. Escreva-o para o
[`/v1/notifications`](/pt-br/guides/notifications), não para uma campanha.

Um template `email` precisa de `subject`; num `in_app` ele é o **título** da linha
no sino, e opcional (uma campanha usa o nome dela); um `whatsapp` não pode ter —
um telefone pareado não tem envelope nem título, e um assunto ali seria um campo
que nada renderiza. O **canal não muda** depois, pela mesma razão: mover uma linha
entre eles teria que inventar ou descartar uma
coluna na mesma escrita.

### Pré-visualize contra uma ficção

```http theme={null}
POST /v1/organization/message-templates/{id}/preview
```

O contato de amostra é fixo, fictício e **completamente preenchido**. Uma prévia
contra um contato real transformaria uma caixa de texto num jeito de ler os
atributos de uma pessoa, e uma prévia com campos vazios pegaria o ramo `{{else}}` de
todo `{{if}}` — uma prévia funcionando de um template quebrado. Como um valor
ausente aparece é o que o seu próprio `{{if}}` disser.

`DELETE` num template **arquiva**. Ele foi para onze mil pessoas e o log de entregas
o nomeia; uma linha que sumisse deixaria esse histórico apontando para nada.
Um `PATCH {"archived": false}` traz de volta.

## O público é sempre um segmento

```bash theme={null}
curl -X POST "https://api.userkit.dev/v1/organization/campaigns?environment=live" \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: org_4b1e…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Win-back",
    "trigger_kind": "segment_entered",
    "segment_id": "…",
    "frequency_cap_days": 30,
    "steps": [
      { "template_id": "…", "offset_hours": 0 },
      { "template_id": "…", "offset_hours": 72 }
    ]
  }'
```

`segment_id` é obrigatório e não existe jeito de dizer "todo mundo" que não seja um
[segmento](/pt-br/guides/segments) que casa com todo mundo. Uma campanha não tem
filtro próprio de propósito: um público definido duas vezes é um público que pode ser
pré-visualizado de um jeito e enviado de outro, sem nada avisando.

Uma campanha sempre nasce **rascunho**, e escrever uma é grátis em todo plano.

## Quatro gatilhos

| Tipo              | Dispara                                                                                                                      | Como                                                |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `one_shot`        | Uma vez, para quem estiver no público no momento em que você arma                                                            | A ativação escreve o público em disco               |
| `event`           | Na primeira vez que um fato nomeado é verdade sobre alguém do público                                                        | Um consumidor do barramento de eventos, em segundos |
| `segment_entered` | Quando alguém entra no **público da própria campanha**                                                                       | O mesmo consumidor                                  |
| `date_offset`     | N dias depois de um carimbo no contato — `created_at`, `first_seen_at`, `identified_at`, `email_verified_at`, `last_seen_at` | Uma varredura diária                                |

Os três primeiros são anunciados por alguma coisa. O quarto não: nada é publicado
quando um relógio cruza uma fronteira, então ele é varrido em vez de consumido. Os
cinco atributos estão no passado, e é por isso que o deslocamento não pode ser
negativo — "três dias antes" é uma frase sobre uma data futura e não há nenhuma aqui
para escrevê-la.

**Uma pessoa é alcançada uma vez por ocasião, para sempre.** O que conta como
ocasião é construído do que a ocasião **é**, nunca do id de uma linha:

* um fato `event` sobre uma **pessoa** é ocasião uma vez — cinco logins são uma
  ocasião;
* um fato `event` sobre um **objeto** é ocasião por objeto — cinco retentativas do
  gateway numa fatura são uma cobrança, e três faturas num ano são três;
* entrar num segmento é uma ocasião: sair e voltar é ruído, não um momento novo;
* um deslocamento de data acontece uma vez, e o **número não faz parte dele** —
  editar de "dia 7" para "dia 14" não pode reenviar para quem já recebeu;
* rearmar um `one_shot` depois de uma pausa **é** ocasião nova, e ela vai alcançar
  quem estiver no público agora.

## Uma sequência

`steps` é a sequência em ordem, até dez. `offset_hours` é medido a partir da
**entrega do passo anterior** — e, no primeiro, a partir da reserva, que é como
"duas horas depois do cadastro" se escreve sem um quinto tipo de gatilho. Um
`one_shot` é uma sequência de um.

Os passos são substituídos **em bloco** a cada escrita. Uma sequência é a definição
do que a campanha diz e quando, então editá-la linha a linha deixaria uma janela em
que uma campanha ativa manda metade de uma sequência e metade de outra.

## Dois limites, duas promessas diferentes

`frequency_cap_days` (padrão 30) é quanto **esta** campanha espera antes de poder
alcançar a mesma pessoa de novo. Atrás dele existe um **período de espera de 24
horas entre todas as campanhas do ambiente**, e é ele que faz a promessa de verdade:
três campanhas obedecendo perfeitamente ao próprio limite são três mensagens numa
hora, e quem recebe experimenta a soma e não as três políticas. Esse período não é
configurável — um botão ali seria abaixado justamente pelo tenant cujo envio é o
problema.

**Nenhum dos dois se aplica aos passos seguintes de uma sequência já começada.** Uma
sequência é uma conversa; o período de espera diz quantas conversas podem começar,
não se uma que começou pode terminar. Aplicado ao passo dois, ele quebraria toda
sequência ao meio em qualquer ambiente com mais de uma campanha — e o teste passaria
perfeitamente.

O limite e o período de espera querem dizer *agora não* e não registram nada. Um
**descadastro** quer dizer *não*, e registra um envio `suppressed` com o motivo.

## O que uma sequência garante

* **Nada é enviado sem antes ser escrito em disco.** Toda mensagem é uma linha
  reservada antes de sair, que é o que torna um disparo retomável: um processo morto
  no destinatário 4.312 continua no 4.313, porque o 4.313 já está gravado.
* **Uma vez por pessoa por ocasião**, faça o barramento o que fizer. Fatos são
  entregues pelo menos uma vez e uma reentrega não escreve nada.
* **A pausa para o que está reservado e ainda não enviado**, não só o que seria
  reservado a seguir. Um disparo de dez mil linhas que não pudesse ser parado no meio
  faria do botão uma mentira.
* **Consentimento e público são reconferidos em cada passo**, nunca herdados do
  primeiro. Quem saiu do público entre duas mensagens deixou de ser para quem a
  campanha é — o segundo aviso de um win-back para quem voltou é a pior mensagem
  deste módulo.
* **Visitantes anônimos nunca são alcançados**, e um passo de `email` precisa de um
  endereço.

## O que ela não garante

* **Não é um relógio.** A entrega e o próximo passo de uma sequência são varridos a
  cada minuto; deslocamentos de data, uma vez por dia. Os deslocamentos são em horas,
  então um minuto está bem dentro da promessa — mas uma mensagem devida às 09:00 sai
  às 09:00 e alguma coisa.
* **Um aniversário com mais de sete dias não é enviado.** Um "bem-vindo à sua segunda
  semana" que chega no segundo mês é pior que nada, e é o que impede uma execução
  perdida por um mês de despejar um ano de aniversários de uma vez.
* **Uma sequência pode terminar antes.** Descadastrar-se, sair do público ou a
  própria ocasião ser resolvida interrompe os passos restantes — de propósito.
* **Um envio que falha é terminal, a não ser que nada pudesse ter saído.** Uma
  retentativa não consegue distinguir uma mensagem de duas, então a falha é
  registrada e não repetida. A única exceção é um envio que não pôde ser
  TENTADO — uma leitura indisponível, uma fila que recusou a publicação —, que
  volta a ser trabalho pendente e vence de novo cinco minutos depois. Nada saiu,
  então não há segunda mensagem a temer.
* **Um envio que não pôde ser tentado tem cinco tentativas, e então para.** As
  esperas dobram — 5, 10, 20, 40 minutos — e cada tentativa registra o que
  encontrou em `last_error`. Uma condição que continua quebrada na quinta não é
  uma indisponibilidade passageira: o envio vira `failed` com essa frase, em vez
  de ser reprocessado para sempre sem ninguém ser avisado. Enquanto ainda tenta,
  as métricas da campanha o contam em `sends.retrying`, e o log de mensagens do
  contato mostra a contagem de tentativas ao lado do status.
* **E-mail, in-app e WhatsApp entregam.** Não há push nem SMS, e não haverá aqui
  um canal sem endpoint por trás. O consentimento é por categoria e não por
  canal, então um opt-out fecha os três — inclusive a linha no sino.

## Armar é o ato que o plano limita

```http theme={null}
POST /v1/organization/campaigns/{id}/activate
POST /v1/organization/campaigns/{id}/pause
```

Escrever a campanha, compor os templates, pré-visualizar e ler qualquer coisa aqui
são grátis em todo plano. **Armar** responde `402
outbound_campaigns_not_included` quando o plano não inclui — uma recusa de plano e
não de permissão, porque `403` é beco sem saída e isto tem saída. Tudo que já foi
escrito continua lá e continua editável.

**Nenhum plano é consultado na saída.** Um plano pode recusar um ato, e parar é o
único ato na frente do qual uma recusa jamais pode ficar.

Num `one_shot`, armar também materializa o público e responde com `claimed` —
quantas pessoas isto vai alcançar. Pausar não é voltar para `draft`: rascunho leria
como "nunca enviou", e o log de envios diz o contrário.

Um `one_shot` que esgota o público se arquiva e publica `campaign.completed`, que é
entregue como [webhook](/pt-br/guides/webhooks) — "o disparo acabou, agora rode o
relatório". Os envios individuais ficam fora do barramento: onze mil entregas de um
disparo seriam uma negação de serviço contra o seu próprio endpoint, e o registro é o
log de entregas.

Apagar uma campanha é um **delete de verdade**, e o log de envios vai junto. É a
resposta honesta para a que foi criada por engano; uma campanha que realmente enviou
alguma coisa deve ser pausada e deixada onde está.

## O que uma campanha fez

```http theme={null}
GET /v1/organization/campaigns/{id}/metrics?window_days=14
```

Os números vêm de três tipos de fonte, e a diferença importa na hora de ler.

**`sends` é a reserva** — escrita antes de qualquer coisa sair, então `sent` e
`suppressed` não conseguem discordar do que foi enviado. `suppressed` é o número que
torna um descadastro visível como resultado em vez de como lacuna.
`retrying` é o número para acompanhar com a campanha rodando: são envios pendentes
esperando um backoff depois de uma tentativa que não pôde ser feita, e cada um tem
no máximo cinco antes de virar `failed`. Um número que só sobe ali é uma causa que
ainda dá para consertar — o `last_error` do envio, no log de mensagens do contato,
diz qual é.

**`delivery` é a resposta do provedor de e-mail, e `tracked` é o campo para ler
primeiro.** Zero de zero rastreados quer dizer *não estamos medindo*; zero de
novecentos quer dizer *ninguém abriu*. Mesma porcentagem, fatos diferentes — que é
por que nenhuma taxa é calculada aqui.

**`outcomes` é um join, não um registro.** Assinaturas e faturas são cruzadas com as
pessoas que a campanha alcançou, dentro de `window_days` depois da mensagem delas.
Mede coincidência numa janela e **nunca causalidade**: quem ia pagar de qualquer
jeito está contado. `reached` é o denominador e deliberadamente não é `sent +
suppressed` — um envio suprimido não alcançou ninguém, e creditar-lhe um upgrade
faria honrar um descadastro parecer marketing que deu certo.

## A campanha que você não criou

Todo ambiente nasce com uma, chamada **Cobrança de pagamento**: os três avisos que
saem quando um pagamento falha, em um, quatro e oito dias. É uma campanha como
qualquer outra no ponto que importa — **as palavras são suas**: reescreva assuntos e
corpos na sua voz, e use `.Billing.InvoiceURL` e `.Billing.GraceUntil` para nomear a
fatura e o prazo.

O que não é seu é o interruptor. Ela não pode ser pausada, apagada, ter o gatilho
trocado nem a sequência refeita, porque pausá-la silencia o único aviso que precede
uma suspensão e o relógio do prazo não para junto. Os templates dela carregam uma
categoria que nenhum contato desliga, e ela é isenta do limite de frequência e do
período de espera global — um win-back de ontem não pode engolir um aviso de
pagamento.

Pagar interrompe: os passos restantes são registrados como `suppressed` com o motivo
`resolved`, que é também a resposta de que um agente de suporte precisa. Uma segunda
fatura falhada é uma segunda sequência.

## Permissão

`messaging:manage` — owner e admin, leituras incluídas. Não é uma parte de
`engagement:manage`, e a separação é deliberada: aquela cobre o que o seu produto
**diz** para quem já está olhando, esta cobre o que ele **envia** para quem não
está. Uma campanha cai numa caixa de entrada de uma pessoa que não abriu nada, não
pode ser editada depois de sair, e errar nela é uma denúncia de spam contra o seu
próprio domínio remetente.
