As palavras são um template
{{.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:
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ê.
{{.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 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: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, ePATCH { "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: 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 comoproduct_news na terça. Veja
mensagens e consentimento 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, 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
{{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
segment_id é obrigatório e não existe jeito de dizer “todo mundo” que não seja um
segmento 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
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
eventsobre uma pessoa é ocasião uma vez — cinco logins são uma ocasião; - um fato
eventsobre 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_shotdepois 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
emailprecisa 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 virafailedcom essa frase, em vez de ser reprocessado para sempre sem ninguém ser avisado. Enquanto ainda tenta, as métricas da campanha o contam emsends.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
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 — “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
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.