Pular para o conteúdo

Rode uma campanha

Uma campanha envia uma sequência de e-mails para uma lista de leads, a partir das caixas de e-mail que você escolhe. Um lead é um contato dentro de uma campanha. Comece por uma chamada que não muda nada: pergunte o que uma campanha precisa para ser iniciada.

Confira antes de iniciar

import { Colding } from "colding";

const colding = new Colding("cd_your_key");
const check = await colding.campaigns.checkLaunch({
  campaignId: "33333333-3333-4333-8333-333333333333",
});
console.log(check.blockers);

checkLaunch recebe o ID de uma campanha, não envia nada e não muda nada. Ela pede a permissão read.

Exemplo para um rascunho novo, sem etapas e sem caixa de e-mail. Ele imprime dois bloqueios.
[
  {
    "code": "no_steps"
  },
  {
    "code": "no_mailboxes"
  }
]

Os blockers (bloqueios) impedem o início até você resolver. Os warnings (avisos) nunca impedem: dizem o que vale saber antes, como ainda não ter leads, a linha de descadastro desligada ou um pagamento que falhou. A resposta traz também a revision, que você passa ao iniciar, e as contagens de leads, etapas e caixas de e-mail.

Um bloqueio é um destes:

no_steps
A sequência não tem nenhuma etapa.
step_empty
Uma etapa está sem mensagem. position diz qual, e 0 é a primeira.
variant_empty
Uma versão ativa de uma etapa está sem mensagem.
no_mailboxes
Nenhuma caixa de e-mail conectada está na lista da campanha.
no_confirmed_mailboxes
Todas as caixas de e-mail da lista têm um endereço que ninguém confirmou ainda. Um dono confirma cada uma no console.
list_invalid_share_high
Leads demais têm um endereço sabidamente inválido. Tire esses leads, como mostra a seção de leads.
workspace_posture
Limitamos o envio deste workspace, então um início não enviaria nada.
campaign_limit
O workspace já roda tantas campanhas quanto o plano permite. Uma campanha pausada não conta. O bloqueio traz limit, running e upgradeTo.

Veja como iniciar

Crie uma campanha

create recebe um nome e cria um rascunho sem etapas, sem leads e sem caixas de e-mail. Nada é enviado até você iniciar. Ela pede a permissão operate.

const campaign = await colding.campaigns.create({
  name: "October introductions",
});
console.log(campaign.id, campaign.status);

kind é sequence, o padrão, ou one_time. Você não troca depois. Uma sequência tem até 50 etapas, então pode ter follow-ups. Um disparo único tem uma etapa. Ele envia essa etapa uma vez, para os leads que tiver ao iniciar, e termina quando eles acabam. Ele nunca cresce sozinho.

Uma campanha nova envia nos dias úteis, no fuso do workspace, no máximo 50 e-mails por dia. A seção de agenda mostra como mudar isso.

Uma chave que um dono limitou a algumas campanhas não cria nenhuma e não edita, pausa, lança nem exclui as outras: a resposta é 403, com details.scope igual a keyCampaign. A menos que um dono tenha dado a ela uma lista de caixas de e-mail e inboxes de agente para ler, ela ainda pode ler todas as campanhas. Uma chave com essa lista não lê nenhuma campanha: a resposta é 403, com details.scope igual a keyReads.

campaigns.list lê suas campanhas, da mais nova para a mais antiga. Passe q para ficar só com as que têm esse texto no nome. Maiúsculas e minúsculas não fazem diferença, mas acentos fazem: cafe não encontra Café.

const found = await colding.campaigns.list({ q: "october" });
console.log(found.items.map((campaign) => campaign.name));

Escreva as etapas

Uma etapa é um e-mail. addStep a coloca no fim da sequência, ou antes, se você passar uma position, em que 0 é a primeira. Ela pede operate.

await colding.campaigns.addStep({
  campaignId: campaign.id,
  subject: "A question about {{ company }}",
  bodyHtml: "<p>Hi {{ first_name }}, are you the right person to ask?</p>",
});
await colding.campaigns.addStep({
  campaignId: campaign.id,
  bodyHtml: "<p>Just checking that my first email reached you.</p>",
  waitDays: 3,
  threadReply: true,
});

subject e bodyHtml são o texto. A parte em texto simples sai do HTML, a menos que você escreva bodyText. Os dois são templates Liquid: o exemplo preenche first_name e company com os dados do contato de cada lead. Spintax, como {Olá|Oi}, é sorteado depois do template. Um template que não é válido responde 400 templateInvalid, e nada é salvo.

waitDays é quantos dias depois da etapa anterior esta sai: 3 se você não disser, no máximo 60. A primeira etapa não tem anterior, então o waitDays dela não é lido.

threadReply: true envia a etapa como resposta na conversa do lead, com o assunto dessa conversa. Se você não disser, um assunto em branco ou repetido responde, e um assunto novo começa uma conversa nova.

Um disparo único mantém uma etapa. Uma segunda responde 409.

Numa campanha em andamento, uma etapa nova ou uma edição de etapa também pede a permissão send. A seção sobre iniciar explica o motivo.

campaigns.updateStep muda o que uma etapa diz ou quando ela sai: envie só o que muda. Uma position nova leva a etapa para lá, e as outras se ajustam em volta. As regras de template acima valem aqui também. campaigns.removeStep exclui uma etapa e não tem volta: as etapas seguintes sobem uma posição. As duas pedem operate. Excluir uma etapa só tira algo, então numa campanha em andamento a aprovação continua e não pede send.

const edited = await colding.campaigns.updateStep({
  campaignId: campaign.id,
  stepId: followUp.id,
  waitDays: 5,
});
console.log(edited.waitDays);

await colding.campaigns.removeStep({ campaignId: campaign.id, stepId: oldStep.id });

campaigns.listTemplates lista seis e-mails para começar, em um idioma: um primeiro e-mail, follow-ups e um último e-mail. Cada um é escrito para o seu idioma, e não traduzido, e traz subject, body, as palavras que você precisa preencher em blanks e o mesmo advice de uma prévia. Ela pede read e não muda nada. language é en, pt-BR ou es, e vale en se você não passar.

const templates = await colding.campaigns.listTemplates({ language: "en" });
const template = templates.find((item) => item.id === "short-question");
if (!template) throw new Error("That template is gone.");
console.log(template.blanks);

const body = template.body
  .replace("[[who you help]]", "freelance designers")
  .replace("[[what you help them do]]", "get paid on time");
await colding.campaigns.addStep({
  campaignId: campaign.id,
  subject: template.subject,
  bodyText: body,
});

As palavras entre colchetes duplos, como [[who you help]], são as lacunas: o que só você pode escrever. Um salvamento que ainda tenha uma lacuna responde 400 stepBlank e diz qual, então uma etapa não é salva com lacuna. A etapa fica com a própria cópia do texto. Nada aponta de volta para o template, e uma resposta pronta de savedReplies.get funciona do mesmo jeito. Copie body como bodyText. Se a etapa já tem bodyHtml, envie também bodyHtml: "", porque a maioria dos apps de e-mail mostra a parte HTML.

previewStep mostra o que uma etapa diz a um lead, montada como o envio a monta: campos preenchidos, spintax na primeira opção e, depois, a assinatura da caixa de e-mail e a linha de descadastro. Ela não envia nem salva nada.

const preview = await colding.campaigns.previewStep({
  campaignId: campaign.id,
  stepId: step.id,
});
console.log(preview.subject);
console.log(preview.advice.issues.map((issue) => issue.code));

O advice da prévia lista o que os filtros de spam podem notar no texto da etapa, sem a assinatura nem a linha de descadastro. Cada ponto tem um code, uma contagem e os trechos que o mostram. É só uma dica: nunca impede uma edição, um início nem um envio, e a conferência antes de iniciar lista as mesmas etapas num aviso content_advice. Quando wordsChecked é false, o texto está num idioma de que não temos lista, ou é curto demais para saber, então não achar palavra de spam não quer dizer nada. As outras conferências foram feitas.

campaigns.testStep envia uma etapa de verdade, para você ver chegar. Ela vai para o endereço que você passar em to, ou para o seu, se não passar nenhum. Ela pede a permissão send. Um endereço que não é o seu precisa antes de uma verificação recente, ou de recipientReviewed: true para um que a verificação não conseguiu confirmar.

const test = await colding.campaigns.testStep({
  campaignId: campaign.id,
  stepId: step.id,
  mailboxId: "22222222-2222-4222-8222-222222222222",
  to: "[email protected]",
});
const send = await colding.sends.get({ sendId: test.id });
console.log(send.status);

Um teste não muda nada na campanha, e o limite diário dela não vale. Ele passa pela lista de supressão, pelo intervalo e pelo limite diário da caixa de e-mail e pelos envios diários do plano. contactId escolhe de quem vêm os campos que preenchem a etapa: o primeiro lead da campanha, se você não passar. variantId envia uma das versões da etapa no lugar da etapa. Uma etapa sem mensagem responde 409 campaignStepEmpty. Leia o resultado com sends.get.

Adicione leads

addLeads recebe uma selection: os IDs dos contatos, ou all: true com filters, os contatos a deixar de fora em exclude e o expectedCount, o número que você conferiu. Se a seleção agora reúne um número diferente de contatos, nada é adicionado e a resposta é 409. Leia a contagem de novo e passe o número novo.

const added = await colding.campaigns.addLeads({
  campaignId: campaign.id,
  selection: { ids: ["11111111-1111-4111-8111-111111111111"] },
});
console.log(added);

Um contato que já é lead entra na conta de alreadyIn e fica como estava. Um contato que se descadastrou, ou que está na lista de supressão, pode ser adicionado, mas a campanha nunca escreve para ele.

Antes de enviar, e quando você adiciona leads a uma campanha em andamento, a campanha confere quantos dos leads que podem receber e-mail têm um endereço sabidamente inválido. Quando pelo menos 50 podem, essa parcela precisa ficar abaixo de 4%, e a conferência avisa a partir de 2%. Uma adição que passaria do limite não adiciona nada e responde 409 leadsInvalidShare. Só conta endereço sabidamente inválido. Um lead que ninguém conferiu não conta, e isso não é uma previsão de bounces.

campaigns.removeLeads tira até 1000 leads pelo ID do contato, ou todo lead sabidamente inválido com invalid: true. Os contatos continuam existindo.

campaigns.listLeads diz como está cada endereço para esta campanha. standing é eligible, review (uma verificação recente disse arriscado ou sem conclusão, e a opção sendToRisky ou sendToUnknown da campanha está desligada), held (espera uma verificação recente) ou excluded (nunca recebe).

q procura cada palavra no endereço, no nome e na empresa, sem diferenciar maiúsculas, e standing filtra a lista. Os leads vêm do último adicionado para o primeiro, uma página por vez, e ler pede read. standingReason diz por que um lead está onde está.

const waiting = await colding.campaigns.listLeads({
  campaignId: campaign.id,
  standing: "held",
});
console.log(waiting.items.map((lead) => lead.email));

const removed = await colding.campaigns.removeLeads({
  campaignId: campaign.id,
  invalid: true,
});
console.log(removed);

Numa campanha em andamento, adicionar leads também pede a permissão send. A seção sobre iniciar explica o motivo.

Pegue leads de um segmento

linkSegment faz de um segmento um dos públicos da campanha. Os contatos que ele reúne agora viram leads. Você passa a revision que leu, e isso é a sua aprovação desse público exato.

const segment = await colding.segments.get({
  segmentId: "55555555-5555-4555-8555-555555555555",
});
await colding.campaigns.linkSegment({
  campaignId: campaign.id,
  segmentId: segment.id,
  revision: segment.revision,
});

const links = await colding.campaigns.listSegments({ campaignId: campaign.id });
console.log(links.map((link) => link.state));

Se o segmento mudou desde então, nada é vinculado e a resposta é 409 segmentStale. Leia de novo e vincule na revisão nova.

Numa campanha de sequência em andamento, os contatos que entrarem no segmento depois também viram leads, conferidos a cada 2 minutos. Vincular um segmento a uma campanha de sequência liga o continuous, então a campanha espera os membros novos em vez de terminar. Passe continuous como false para mudar isso. Um disparo único leva os membros que existem ao iniciar, e mais nenhum. Uma campanha vincula até 10 segmentos.

campaigns.listSegments pede read e responde os segmentos vinculados, cada um com a approvedRevision que alguém aprovou, quem aprovou e um state:

covered
O segmento continua na revisão aprovada, então os contatos que entrarem nele viram leads.
edited
O segmento mudou desde então. Vincule de novo na revisão nova para aprovar.
held
Os leads novos ficaram em espera. heldReason diz o motivo: list_gate, key_bounds, approver_lapsed ou checks_unavailable. Vincule de novo e os leads novos saem da espera.

Cada lead passa pelas mesmas conferências de addLeads. Editar um segmento nunca muda o que uma campanha em andamento envia: só deixa em espera os leads novos desse vínculo até alguém aprovar de novo. Um contato que você tirou à mão continua fora.

unlinkSegment tira os leads que esse vínculo trouxe e que ninguém recebeu e-mail. Um lead que já recebeu fica, e um que você adicionou à mão também. Um lead que outro segmento vinculado ainda inclui também fica.

Vincular pede operate enquanto a campanha é rascunho, e send depois que ela tem uma aprovação. Você passa a ser quem aprovou.

Escolha as caixas de e-mail

setSenders troca a lista de caixas de e-mail de onde a campanha envia por exatamente os IDs que você passar, até 100. Cada uma precisa ser uma caixa conectada neste workspace, ou a resposta é 404. Uma caixa reservada a e-mails transacionais responde 409, porque campanhas só enviam de caixas de campanhas e mensagens (purpose: "cold"). Uma lista vazia significa que a campanha não envia de nenhuma, nunca de todas.

await colding.campaigns.setSenders({
  campaignId: campaign.id,
  mailboxIds: ["66666666-6666-4666-8666-666666666666"],
});

Um lead mantém a caixa de onde saiu o primeiro e-mail, então cada etapa chega à pessoa do mesmo endereço. Se essa caixa não pode enviar por enquanto, o lead espera por ela. Uma caixa cujo endereço ninguém confirmou fica de fora, e os leads dela vão para as outras.

Para conectar uma caixa de e-mail, peça a um dono por uma solicitação, como mostra o primeiro guia.

Defina a agenda e o ritmo

update muda as configurações de uma campanha. Envie só o que muda. Ela pede operate. Estes são os campos que definem o ritmo e para quem ela escreve:

await colding.campaigns.update({
  campaignId: campaign.id,
  dailyLimit: 40,
  schedule: {
    mon: [{ start: "09:00", end: "12:00" }],
    tue: [{ start: "09:00", end: "12:00" }],
    wed: [{ start: "09:00", end: "12:00" }],
    thu: [{ start: "09:00", end: "12:00" }],
    fri: [{ start: "09:00", end: "12:00" }],
    sat: [],
    sun: [],
  },
});
schedule
Todos os dias da semana, de mon a sun, com até 4 janelas de envio cada um, como HH:MM no fuso do workspace. Uma lista vazia é um dia de folga, e pelo menos um dia precisa de janela.
dailyLimit
E-mails por dia somando todas as caixas. Campanhas novas começam em 50, e o máximo que você pode definir é 5000.
rampStart
O número de e-mails do primeiro dia. Depois sobe rampIncrement a cada dia em que a campanha enviou, até o dailyLimit. null desliga.
maxNewLeadsPerDay
Limita os leads que recebem o primeiro e-mail num dia. Follow-ups nunca contam. null significa sem limite.
entryDelayMinutes
É a espera antes do primeiro e-mail: faz o lead esperar depois de ser adicionado, até 90 dias.
newLeadsFirst
Envia os primeiros e-mails antes dos follow-ups. Desligado, os follow-ups saem primeiro.
continuous
Mantém a campanha rodando quando todos os leads terminam, esperando os leads que você adicionar. Desligado, ela termina. Uma campanha sem nenhum lead ainda espera, seja qual for o tipo. Um disparo único não pode ser contínuo: continuous: true num deles responde 409 oneTimeNotContinuous.
stopOnReply
A resposta de uma pessoa encerra a sequência daquele lead.
sendToRiskysendToUnknown
sendToRisky e sendToUnknown deixam o primeiro e-mail ir para um endereço que uma verificação chamou de arriscado, ou não soube julgar. Os dois vêm desligados, e esses leads esperam revisão.
ccbcc
cc e bcc aceitam até 5 endereços cada, que também recebem todo e-mail. O lead vê o Cc e nunca o Cco. O e-mail deixa de fora o endereço do próprio lead e qualquer endereço que esteja na lista de supressão ou seja sabidamente inválido. As cópias não contam em nenhum limite diário aqui, mas o seu provedor de e-mail conta cada uma como mais um destinatário.

Inicie uma campanha

Ao iniciar, a campanha começa a enviar e você aprova exatamente o que ela envia. Isso pede a permissão send, e você passa a revision que a conferência respondeu.

const check = await colding.campaigns.checkLaunch({
  campaignId: campaign.id,
});
if (check.blockers.length === 0) {
  const launched = await colding.campaigns.launch({
    campaignId: campaign.id,
    revision: check.revision,
  });
  console.log(launched.status);
}

O dono do workspace também precisa ter aceitado a política de uso aceitável. Antes disso, iniciar, testStep e uma edição que aumenta o que uma campanha em andamento envia respondem 403 AUP_REQUIRED. O primeiro guia explica o que fazer.

Se a campanha mudou depois da conferência, a resposta é 409 campaignChanged e nada começa. Se sobrou um bloqueio, a resposta é 409 campaignNotReady, com os bloqueios em details. Quando o único bloqueio é campaign_limit, a resposta é 402 PLAN_GATE.

Você passa a ser quem aprovou a campanha, e os e-mails saem em seu nome. Se você perder a permissão send, ou a chave for revogada, a campanha pausa como approval_lapsed. Um dono pode limitar uma chave a certas caixas, campanhas e fluxos, e a um número de e-mails. A campanha precisa caber nesses limites, ou o início responde 403 com details.scope.

Iniciar uma campanha em andamento na revisão atual não muda nada. Iniciar uma campanha pausada a retoma. Iniciar uma concluída a reabre, e é assim que você envia para leads adicionados depois que ela terminou.

Com a permissão validate, iniciar também começa a conferir os endereços dos leads que ainda não receberam e-mail e não têm veredito, ou têm um antigo. Passe checkLeads: false para pular só as conferências desse início.

Depois de iniciar, uma mudança que só tira algo não pede mais nada. Qualquer outra pede a permissão send e faz de você quem aprovou. Por exemplo:

  • Um ritmo mais rápido: dailyLimit, maxNewLeadsPerDay, rampStart ou rampIncrement maiores, ou um entryDelayMinutes menor.
  • Uma mudança em schedule, textOnly ou unsubscribeMode, o rastreamento ligado ou o stopOnReply desligado.
  • Uma etapa nova, ou o texto, a espera ou a ordem de uma etapa alterados.
  • Uma caixa de e-mail adicionada à lista.
  • Um endereço novo em Cc ou Cco, ou um que passou de Cco para Cc.
  • Adicionar leads ou vincular um segmento.
  • Uma versão nova de uma etapa, um nome, uma mensagem ou um peso alterados, uma versão pausada retomada ou um vencedor escolhido.
  • Ligar sendToRisky ou sendToUnknown.

Estas só tiram algo, então mantêm a aprovação e não pedem send: um nome novo; um dailyLimit, rampStart, rampIncrement ou limite de leads novos menores, ou definir um limite; uma espera antes do primeiro e-mail maior; stopOnReply ligado; rastreamento, sendToRisky ou sendToUnknown desligados; newLeadsFirst ou continuous mudados, para qualquer lado; um endereço tirado de Cc ou Cco; uma etapa, uma caixa de e-mail, leads ou um segmento tirados; uma versão pausada ou arquivada.

Pause, retome, acompanhe o fim e exclua

O status de uma campanha é draft, active, paused ou completed.

pause para uma campanha em andamento agora. Os e-mails que ainda não começaram a sair são cancelados, e os leads deles recebem essas etapas de novo quando ela retomar. Configurações, etapas e leads ficam. Um rascunho ou uma campanha concluída responde 409 campaignNotRunning. Para retomar, inicie de novo, como acima. Ela pede operate.

const paused = await colding.campaigns.pause({
  campaignId: campaign.id,
});
console.log(paused.status, paused.pausedReason);

Uma campanha em andamento também pode pausar sozinha. pausedReason diz o motivo, e é nulo quando uma pessoa pausou:

no_mailboxes
Não sobrou nenhuma caixa de e-mail conectada na lista.
no_confirmed_mailboxes
Todas as caixas da lista têm um endereço que ninguém confirmou. O dono do workspace confirma uma no console.
domain_failing
Todas as caixas da lista enviam de um domínio que falha nos registros.
approval_lapsed
Quem iniciou não pode mais enviar, ou a campanha mudou de um jeito que ninguém aprovou.
guardrail
Pelo menos 50 e-mails saíram em 7 dias, e 5% deram bounce ou 0,1% foram denunciados como spam. Depois dessa pausa, a contagem recomeça do zero.
list_quality
A parcela de leads com endereço sabidamente inválido chegou a 4%. listGateBreach traz as contagens.
staff
Nossa equipe parou a campanha à mão. staffPauseReason diz o motivo.
workspace_closing
O dono do workspace pediu para excluí-lo. Iniciar fica recusado até ele cancelar o pedido.

Resolva a causa e inicie de novo.

Uma campanha de sequência termina quando todos os leads terminam. Com continuous ligado, ela continua rodando e espera os leads que você adicionar. Uma campanha sem nenhum lead ainda também espera, seja qual for o tipo, e waitingForLeadsSince diz desde quando. Um disparo único não pode ser contínuo: ele termina quando os leads dele terminam.

campaigns.remove exclui uma campanha com as etapas, os leads e a lista de caixas de e-mail. Ela pede operate e não tem volta. Os contatos continuam, e quem se descadastrou por ela continua descadastrado. Os e-mails que ela já enviou continuam na lista de envios, com campaignId em null.

listLog lê o que aconteceu com uma campanha, do mais novo para o mais antigo: o que pessoas e chaves mudaram e o que a campanha fez sozinha, como uma pausa e o motivo, um término, um lead que ela deixou de fora ou uma cópia que ela tirou. Guarda 90 dias.

Passe kinds para o listLog ler um grupo só: changes (edições, etapas, caixas de e-mail, leads adicionados ou removidos, a pausa de um lead e segmentos vinculados), status (inícios, pausas, términos e espera por leads) ou leads (um lead deixado de fora ou movido e uma cópia retirada). Sem ele, vem o histórico inteiro.

const history = await colding.campaigns.listLog({
  campaignId: campaign.id,
  kinds: "status",
});
for (const entry of history.items) console.log(entry.at, entry.type);

Acompanhe um lead

getLead diz o que a campanha faz a seguir com um lead, em next.state: due (a espera do lead acabou; os horários, limites e pausas da campanha ainda valem), waits ou ended. Ela pede a permissão read.

const lead = await colding.campaigns.getLead({
  campaignId: campaign.id,
  contactId,
});
console.log(lead.next);

await colding.campaigns.holdLead({
  campaignId: campaign.id,
  contactId,
  until: "2026-10-12T09:00:00-03:00",
});

await colding.campaigns.releaseLead({ campaignId: campaign.id, contactId });

Quando um lead está em waits, why é um destes:

step_on_its_way
Um e-mail está a caminho.
step_wait
A próxima etapa espera os dias dela depois da anterior.
entry_delay
Um lead novo espera o tempo definido para o primeiro e-mail da campanha.
away
Uma mensagem de fora do escritório disse que a pessoa volta mais tarde.
held
Uma pessoa pausou este lead.
new_leads_cap
Os primeiros e-mails de hoje acabaram. Ele começa amanhã.
address_check
O endereço espera uma verificação, ou que a campanha aceite um endereço arriscado ou sem conclusão.
mailbox_busy
A caixa de e-mail com a qual ele conversa não pode enviar agora.

Quando um lead está em ended, why é um destes:

sequence_done
Ele recebeu todas as etapas que podia.
replied
Ele respondeu, e a campanha para quando há resposta.
replied_elsewhere
A pessoa respondeu a outra campanha do workspace, e esta campanha para quando há resposta. O repliedElsewhere do lead diz qual.
suppressed
O endereço está na lista de supressão, ou a pessoa se descadastrou.
invalid
Um serviço de verificação ou o seu arquivo de contatos disse que o endereço não existe.
failed
A próxima etapa não pôde ser enviada, então este lead parou aqui.

A resposta de uma pessoa também encerra os leads dela nas outras campanhas do workspace, a menos que o dono tenha desligado isso em Configurações. Vem ligado por padrão. Vale só para as campanhas que existem quando a resposta chega, então uma campanha que você criar depois não é afetada, e nunca encerra um lead cuja campanha continua escrevendo depois de uma resposta. Uma mensagem de fora do escritório, uma resposta que pede o descadastro e a resposta de uma cópia (Cc ou Cco) não fazem isso. Os resultados do próprio lead não contam essa resposta, e o repliedAt dele continua null. O listLog mostra uma linha lead_replied_elsewhere para cada lead que ela encerrou.

holdLead pausa um lead: nada mais sai dele nesta campanha até until, uma data e hora com o fuso, que ainda esteja no futuro (uma data passada responde 400 leadHoldEndPassed). Sem until, vale até o campaigns.releaseLead. Um e-mail já preparado para ele que ainda não começou a sair é cancelado. Essa pausa é separada da espera por mensagem de fora do escritório, e a etapa espera até acabar a mais longa das duas. Ela pede operate e mantém a aprovação, porque não muda nada do que a campanha envia.

campaigns.releaseLead encerra a pausa de um lead: a próxima etapa pode sair na hora, se a espera da própria etapa já acabou. A espera por mensagem de fora do escritório continua. Um lead que ninguém pausou fica como estava. Ela pede operate.

Um lead pode esperar pela verificação do endereço mesmo quando você sabe que ele recebe e-mails. Só uma pessoa pode dizer isso: o sendAnyway do lead, que listLeads e getLead devolvem, mostra quem fez e o que foi ignorado, e nenhuma chave consegue defini-lo. Para pedir a uma pessoa, crie uma solicitação do tipo send_lead_anyway, o campaignId e o contactId do lead, e diga em note por que você espera que o endereço receba e-mails. A pessoa abre o lead no console e envia mesmo assim, ou recusa. A solicitação passa então a completed ou cancelled. Ela responde 409 para um endereço na lista de supressão, um endereço considerado inválido, um cuja verificação ainda está em andamento e um para o qual a campanha já escreve. Ninguém consegue enviar por cima dos dois primeiros.

Teste duas versões de uma etapa

addStepVariant adiciona outra versão da mensagem de uma etapa. O texto da própria etapa é o original, e uma etapa tem até 4 versões além dele, as pausadas incluídas. Arquive uma para abrir espaço. Cada lead sorteia uma versão da etapa, uma vez, na proporção dos pesos, e fica com ela: o mesmo lead sempre recebe a mesma. O peso do original é o weight da etapa, e o de uma versão nova é 50, se você não passar um.

const variant = await colding.campaigns.addStepVariant({
  campaignId: campaign.id,
  stepId: step.id,
  subject: "Quick question for {{ first_name }}",
  bodyHtml: "<p>Are you the right person to ask about this?</p>",
});

await colding.campaigns.pickStepWinner({
  campaignId: campaign.id,
  stepId: step.id,
  variantId: variant.id,
});

Numa etapa que responde na mesma conversa, uma versão muda só a mensagem. Um assunto nela responde 400 variantSubjectThreading. Uma versão pausada não é sorteada, e se todas estiverem pausadas o original é enviado. Uma etapa guarda até 20 versões arquivadas para os resultados.

campaigns.updateStepVariant muda o nome, o texto ou o peso de uma versão, ou a pausa com paused: true e a retoma com paused: false. Envie só o que muda. Retomar pede uma mensagem no corpo. Um lead que já sorteou uma versão fica com ela enquanto ela puder enviar, e sorteia de novo quando a versão é pausada. Qualquer mudança que não seja só uma pausa aumenta o que a campanha envia, então numa campanha em andamento ela pede send, como diz a seção sobre iniciar. campaigns.removeStepVariant arquiva uma versão: os números dela ficam nos resultados e o nome fica livre de novo. Os leads que a sortearam sorteiam de novo entre as outras, se ainda tiverem uma etapa para enviar. Não dá para trazê-la de volta: adicione uma nova. As duas pedem operate.

await colding.campaigns.updateStepVariant({
  campaignId: campaign.id,
  stepId: step.id,
  variantId: "77777777-7777-4777-8777-777777777777",
  paused: true,
});

await colding.campaigns.removeStepVariant({
  campaignId: campaign.id,
  stepId: step.id,
  variantId: "88888888-8888-4888-8888-888888888888",
});

Nada escolhe um vencedor por você. pickStepWinner recebe o ID de uma versão, ou original. A mensagem escolhida vira a da etapa, a etapa volta a valer para todos os leads e todas as versões são arquivadas. Os números delas ficam nos resultados. O console diz que ainda é cedo para concluir até uma versão ter enviado e-mail a 100 pessoas.

Uma versão nova, um nome, uma mensagem ou um peso alterados, uma versão pausada retomada e um vencedor aumentam o que uma campanha em andamento envia, então pedem a permissão send, como diz a seção sobre iniciar. Pausar ou arquivar uma versão não pede.

Leia os resultados

get devolve a campanha com o engagement, contado em pessoas, não em e-mails: emailed, opened, clicked e replied. Um lead que abriu cinco vezes conta uma.

const { engagement } = await colding.campaigns.get({
  campaignId: campaign.id,
});
console.log(engagement.emailed, engagement.opened, engagement.replied);

Só conta o que uma pessoa fez. Ficam de fora um scanner de e-mail, um proxy de imagens, qualquer coisa cedo demais depois que o e-mail saiu e vários links seguidos da mesma rede em poucos segundos. openedByMachinesOnly conta os leads cujos e-mails só máquinas abriram, o que explica por que opened pode parecer baixo.

Uma abertura é a imagem do e-mail carregando, então quem lê com as imagens desligadas nunca conta. opened é um piso, não uma taxa de leitura.

Aberturas e cliques só contam enquanto a campanha os rastreia e um dos domínios de rastreamento do workspace está em uso. Os dois vêm desligados. Até lá, um 0 quer dizer que nada foi medido.

steps repete as contagens por etapa, sem respostas, porque uma resposta é da campanha e não de uma etapa. Cada etapa com versões também lista as contagens de cada uma. links lista as páginas em que as pessoas chegaram pelos cliques, no máximo 10, nos últimos 90 dias.

Veja qual domínio de rastreamento está em uso

Aberturas e cliques são medidos por um domínio de rastreamento: um nome como t.acme.com com um registro CNAME que aponta para os nossos servidores. trackingDomains.list lista os domínios de rastreamento do workspace, do mais antigo para o mais novo. Ela pede read. Cada um traz o hostname, o target para onde o registro aponta, um status e inUse.

const domains = await colding.trackingDomains.list();
for (const domain of domains) console.log(domain.hostname, domain.status, domain.inUse);

const pending = domains.find((domain) => domain.status === "pending");
if (pending) {
  const checked = await colding.trackingDomains.check({ trackingDomainId: pending.id });
  console.log(checked.status, checked.lastCheck);
}

status é pending até o registro ser visto, verified quando ele aponta para o destino e failing quando o registro de um domínio verificado sumiu ou aponta para outro lugar. Os e-mails de uma campanha levam a imagem de rastreamento e os links rastreados só enquanto um domínio está em uso (inUse): ele está verificado, o nosso lado do destino responde (targetResolves não é false) e vimos o nosso lado servir o nome por HTTPS (httpsOkAt tem valor). Essa última parte é nossa de preparar, nunca sua. Enquanto nenhum domínio está em uso, os e-mails saem sem a imagem e com os links como foram escritos, e um 0 nos resultados quer dizer que nada foi medido. Cada e-mail usa o domínio de rastreamento que fica sob o domínio de onde ele sai (t.acme.io para um e-mail de [email protected]), ou o mais antigo em uso quando não há nenhum.

trackingDomains.check olha o registro de um domínio agora, sem esperar a conferência de cada hora, e responde o domínio como ele fica. lastCheck diz o que o DNS respondeu: verified, no_record, wrong_target (com observed, para onde ele leva) ou dns_unanswered. Um domínio verificado só deixa de ser usado quando o DNS responde que o registro sumiu ou aponta para outro lugar, nunca porque o DNS não respondeu. Quando o registro é verificado, conferimos em poucos minutos que o nosso lado serve o nome por HTTPS, e depois a cada hora. Ela pede operate.

Um workspace pode verificar no máximo 10 nomes de host novos em 30 dias. Passando disso, uma conferência que chegaria a verificar responde 429 até o mais antigo sair da janela, e nada é verificado. A conferência também conta no limite do workspace para as conferências de caixa de e-mail, por minuto e por hora, e passando desse limite ela responde 429 com Retry-After.

Uma chave não pode adicionar nem remover um domínio de rastreamento: isso pede manage, que uma chave nunca tem, então uma pessoa faz isso em Configurações. Um workspace tem até 10. Enquanto um domínio não está verificado, dnsHost nomeia o provedor de DNS que guarda a zona dele, ou é null quando o DNS não respondeu a tempo.

Eventos que uma campanha gera

Um webhook ou events.list recebe estes quando uma campanha faz algo. O guia de webhooks mostra como recebê-los.

campaign.reply_received
Alguém respondeu a um e-mail de campanha, ou o servidor da pessoa respondeu por ela.
campaign.paused
Uma campanha em andamento foi pausada. O evento diz o motivo, a menos que uma pessoa ou uma chave tenha pausado.
campaign.finished
Todos os leads receberam a sequência inteira, então a campanha terminou.
campaign.waiting_for_leads
Uma campanha em andamento ficou sem ninguém para escrever.

campaign.waiting_for_leads dispara uma vez a cada vez que uma campanha passa a esperar. O campo continuous é true quando a campanha continua rodando à espera de leads novos, e false quando ela não tinha lead nenhum.

Todo evento send.* traz um campaignId: o da campanha que enviou, ou null para um e-mail que não era de campanha. Eventos de antes desse campo não o têm, então trate como opcional.

Leia o guia de webhooks