Saltar al contenido

Ejecuta una campaña

Una campaña envía una secuencia de correos a una lista de leads, desde los buzones que elijas. Un lead es un contacto dentro de una campaña. Empieza con una llamada que no cambia nada: pregunta qué le falta a una campaña para poder iniciarse.

Comprueba 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 recibe el ID de una campaña, no envía nada y no cambia nada. Pide el permiso read.

Ejemplo para un borrador nuevo, sin pasos y sin buzón. Imprime dos bloqueos.
[
  {
    "code": "no_steps"
  },
  {
    "code": "no_mailboxes"
  }
]

Los blockers (bloqueos) impiden iniciar hasta que los resuelvas. Los warnings (avisos) nunca lo impiden: dicen lo que conviene saber antes, como que aún no hay leads, que la línea de baja está apagada o que un pago falló. La respuesta trae además la revision, que pasas al iniciar, y los conteos de leads, pasos y buzones.

Un bloqueo es uno de estos:

no_steps
La secuencia no tiene ningún paso.
step_empty
Un paso no tiene mensaje. position dice cuál, y 0 es el primero.
variant_empty
Una versión activa de un paso no tiene mensaje.
no_mailboxes
Ningún buzón conectado está en la lista de la campaña.
no_confirmed_mailboxes
Todos los buzones de la lista tienen una dirección que nadie ha confirmado todavía. Un dueño confirma cada una en la consola.
list_invalid_share_high
Demasiados leads tienen una dirección que se sabe inválida. Quítalos, como muestra la sección de leads.
workspace_posture
Limitamos el envío de este workspace, así que iniciar no enviaría nada.
campaign_limit
El workspace ya ejecuta tantas campañas como permite su plan. Una campaña en pausa no cuenta. El bloqueo trae limit, running y upgradeTo.

Mira cómo iniciar

Crea una campaña

create recibe un nombre y crea un borrador sin pasos, sin leads y sin buzones. No se envía nada hasta que inicias. Pide el permiso operate.

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

kind es sequence, el valor por defecto, o one_time. No se cambia después. Una secuencia tiene hasta 50 pasos, así que puede tener siguientes pasos. Un envío único tiene un paso. Envía ese paso una vez, a los leads que tenga al iniciar, y termina cuando ellos acaban. Nunca crece solo.

Una campaña nueva envía en días hábiles, en la zona horaria del workspace, como máximo 50 correos al día. La sección de horario muestra cómo cambiarlo.

Una clave que un dueño limitó a algunas campañas no crea ninguna y no edita, pausa, lanza ni elimina las demás: responde 403, con details.scope igual a keyCampaign. A menos que un dueño le haya dado una lista de buzones y bandejas de entrada de agente para leer, aun así puede leer todas las campañas. Una clave con esa lista no lee ninguna campaña: responden 403, con details.scope igual a keyReads.

campaigns.list lee tus campañas, de la más nueva a la más vieja. Pásale q para quedarte solo con las que tienen ese texto en el nombre. Las mayúsculas no importan, pero los acentos sí: cafe no encuentra Café.

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

Escribe los pasos

Un paso es un correo. addStep lo pone al final de la secuencia, o antes si das una position, donde 0 es el primero. Pide 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 y bodyHtml son el texto. La parte de texto simple sale del HTML, salvo que escribas bodyText. Los dos son plantillas Liquid: el ejemplo rellena first_name y company con los datos del contacto de cada lead. El spintax, como {Hola|Buenas}, se sortea después de la plantilla. Una plantilla que no es válida responde 400 templateInvalid, y no se guarda nada.

waitDays es cuántos días después del paso anterior sale este: 3 si no lo indicas, como máximo 60. El primer paso no tiene anterior, así que su waitDays no se lee.

threadReply: true envía el paso como respuesta en la conversación del lead, con el asunto de esa conversación. Si no lo indicas, un asunto vacío o repetido responde, y un asunto nuevo abre una conversación nueva.

Un envío único mantiene un solo paso. Un segundo responde 409.

En una campaña en curso, un paso nuevo o la edición de un paso también pide el permiso send. La sección sobre iniciar explica por qué.

campaigns.updateStep cambia lo que dice un paso o cuándo sale: envía solo lo que cambia. Una position nueva lleva el paso allí, y los demás se acomodan alrededor. Las reglas de plantilla de arriba valen aquí también. campaigns.removeStep elimina un paso y no se puede deshacer: los pasos siguientes suben un lugar. Los dos piden operate. Eliminar un paso solo quita algo, así que en una campaña en curso mantiene la aprobación y no pide 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 correos para empezar, en un idioma: un primer correo, siguientes pasos y un último correo. Cada uno está escrito para su idioma, no traducido, y trae subject, body, las palabras que debes completar en blanks y el mismo advice de una vista previa. Pide read y no cambia nada. language es en, pt-BR o es, y vale en si no lo indicas.

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,
});

Las palabras entre corchetes dobles, como [[who you help]], son los espacios en blanco: lo que solo tú puedes escribir. Un guardado que todavía tenga uno responde 400 stepBlank y dice cuál, así que un paso no se guarda con un espacio en blanco. El paso se queda con su propia copia del texto. Nada apunta de vuelta a la plantilla, y una respuesta guardada de savedReplies.get funciona igual. Copia body como bodyText. Si el paso ya tiene bodyHtml, envía también bodyHtml: "", porque la mayoría de las apps de correo muestran la parte HTML.

previewStep muestra lo que un paso le dice a un lead, armado como lo arma el envío: campos rellenados, spintax en su primera opción y, después, la firma del buzón y la línea de baja. No envía ni guarda 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));

El advice de la vista previa lista lo que pueden notar los filtros de spam en el texto del paso, sin la firma ni la línea para darse de baja. Cada punto tiene un code, un conteo y los lugares que lo muestran. Es solo un consejo: nunca impide una edición, un inicio ni un envío, y la comprobación antes de iniciar lista los mismos pasos en un aviso content_advice. Cuando wordsChecked es false, el texto está en un idioma del que no tenemos lista, o es demasiado corto para saberlo, así que no encontrar ninguna palabra de spam no significa nada. Las demás comprobaciones sí se hicieron.

campaigns.testStep envía un paso de verdad, para que lo veas llegar. Va a la dirección que pases en to, o a la tuya si no pasas ninguna. Pide el permiso send. Una dirección que no es tuya necesita antes una comprobación reciente, o recipientReviewed: true para una que la comprobación no pudo 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);

Una prueba no cambia nada en la campaña, y el límite diario de la campaña no cuenta. Sí pasa por la lista de supresión, el intervalo y el límite diario del buzón, y los envíos diarios del plan. contactId elige de quién salen los campos que rellenan el paso: el primer lead de la campaña, si no lo pasas. variantId envía una de las versiones del paso en lugar del paso. Un paso sin mensaje responde 409 campaignStepEmpty. Lee el resultado con sends.get.

Agrega leads

addLeads recibe una selection: los IDs de los contactos, o all: true con filters, los contactos que quedan fuera en exclude y expectedCount, el número que confirmaste. Si ahora coincide otra cantidad de contactos, no se agrega nada y responde 409. Lee el conteo de nuevo y pasa el número nuevo.

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

Un contacto que ya es lead se cuenta en alreadyIn y queda como estaba. Un contacto que se dio de baja, o que está en la lista de supresión, se puede agregar, pero la campaña nunca le escribe.

Antes de enviar, y cuando agregas leads a una campaña en curso, la campaña comprueba cuántos de los leads que pueden recibir correo tienen una dirección que se sabe inválida. Cuando al menos 50 pueden, esa parte tiene que quedar por debajo de 4%, y la comprobación avisa desde 2%. Una adición que pasaría el límite no agrega nada y responde 409 leadsInvalidShare. Solo cuenta la dirección que se sabe inválida. Un lead que nadie comprobó no cuenta, y esto no es una previsión de rebotes.

campaigns.removeLeads quita hasta 1000 leads por ID de contacto, o todo lead que se sabe inválido con invalid: true. Los contactos siguen existiendo.

campaigns.listLeads dice cómo está cada dirección para esta campaña. standing es eligible, review (una comprobación reciente dijo riesgosa o sin conclusión, y el ajuste sendToRisky o sendToUnknown de la campaña está apagado), held (espera una comprobación reciente) o excluded (nunca recibe).

q busca cada palabra en la dirección, el nombre y la empresa, sin distinguir mayúsculas, y standing filtra la lista. Los leads salen del último agregado al primero, una página a la vez, y leerlos pide read. standingReason dice por qué un lead está donde 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);

En una campaña en curso, agregar leads también pide el permiso send. La sección sobre iniciar explica por qué.

Toma leads de un segmento

linkSegment hace de un segmento una de las audiencias de la campaña. Los contactos que reúne ahora se vuelven leads. Pasas la revision que leíste, y eso es tu aprobación de esa audiencia exacta.

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));

Si el segmento cambió desde entonces, no se vincula nada y responde 409 segmentStale. Léelo de nuevo y vincúlalo con la revisión nueva.

En una campaña de secuencia en curso, los contactos que entren al segmento después también se vuelven leads, comprobados cada 2 minutos. Vincular un segmento a una campaña de secuencia enciende continuous, así que la campaña espera a los miembros nuevos en vez de terminar. Pasa continuous en false para cambiarlo. Un envío único toma los miembros que tiene al iniciar, y ninguno más. Una campaña vincula hasta 10 segmentos.

campaigns.listSegments pide read y responde los segmentos vinculados, cada uno con la approvedRevision que alguien aprobó, quién la aprobó y un state:

covered
El segmento sigue en la revisión aprobada, así que los contactos que entren en él se vuelven leads.
edited
El segmento cambió desde entonces. Vincúlalo de nuevo con su revisión nueva para aprobarlo.
held
Los leads nuevos quedaron en espera. heldReason dice el motivo: list_gate, key_bounds, approver_lapsed o checks_unavailable. Vincúlalo de nuevo para que vuelvan a entrar.

Cada lead pasa por las mismas comprobaciones que addLeads. Editar un segmento nunca cambia lo que envía una campaña en curso: solo deja en espera los leads nuevos de ese vínculo hasta que alguien lo apruebe de nuevo. Un contacto que quitaste a mano sigue fuera.

unlinkSegment quita los leads que ese vínculo trajo y a los que nadie ha escrito. Un lead que ya recibió correo se queda, y uno que agregaste a mano también. Un lead que otro segmento vinculado todavía incluye también se queda.

Vincular pide operate mientras la campaña es un borrador, y send cuando ya tiene una aprobación. Pasas a ser quien la aprobó.

Elige los buzones

setSenders reemplaza la lista de buzones desde los que envía la campaña por exactamente los IDs que pases, hasta 100. Cada uno tiene que ser un buzón conectado en este workspace, o responde 404. Un buzón reservado para correos transaccionales responde 409, porque las campañas solo envían desde buzones de campañas y mensajes (purpose: "cold"). Una lista vacía significa que la campaña no envía desde ninguno, nunca desde todos.

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

Un lead conserva el buzón desde el que salió su primer correo, así que cada paso le llega a la persona desde la misma dirección. Si ese buzón no puede enviar por ahora, el lead lo espera. Un buzón cuya dirección nadie confirmó queda fuera, y sus leads pasan a los demás.

Para conectar un buzón, pídeselo a un dueño con una solicitud de acción, como muestra la primera guía.

Define el horario y el ritmo

update cambia los ajustes de una campaña. Envía solo lo que cambia. Pide operate. Estos son los campos que marcan el ritmo y a quién escribe:

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
Cada día de la semana, de mon a sun, con hasta 4 ventanas de envío cada uno, como HH:MM en la zona horaria del workspace. Una lista vacía es un día libre, y al menos un día necesita una ventana.
dailyLimit
Correos al día entre todos sus buzones. Las campañas nuevas empiezan en 50, y lo máximo que puedes poner es 5000.
rampStart
Los correos del primer día. Después sube rampIncrement cada día que la campaña envió, hasta dailyLimit. null lo apaga.
maxNewLeadsPerDay
Limita los leads que reciben su primer correo en un día. Los siguientes pasos nunca cuentan. null significa sin límite.
entryDelayMinutes
Es la espera antes del primer correo: hace que un lead espere después de agregarse, hasta 90 días.
newLeadsFirst
Envía los primeros correos antes que los siguientes pasos. Apagado, salen primero los siguientes pasos.
continuous
Mantiene la campaña en marcha cuando todos los leads terminan, y espera los leads que agregues. Apagado, termina. Una campaña sin ningún lead todavía espera, sea del tipo que sea. Un envío único no puede ser continuo: continuous: true en uno responde 409 oneTimeNotContinuous.
stopOnReply
La respuesta de una persona termina la secuencia de ese lead.
sendToRiskysendToUnknown
sendToRisky y sendToUnknown dejan que un primer correo vaya a una dirección que una comprobación llamó riesgosa, o que no supo juzgar. Los dos vienen apagados, y esos leads esperan revisión.
ccbcc
cc y bcc aceptan hasta 5 direcciones cada uno, que también reciben todos los correos. El lead ve el CC y nunca el CCO. El correo deja fuera la dirección del propio lead y cualquiera que esté en la lista de supresión o se sepa inválida. Las copias no cuentan en ningún límite diario aquí, pero tu proveedor de correo cuenta cada una como un destinatario más.

Inicia una campaña

Iniciar pone en marcha la campaña y aprueba exactamente lo que envía. Pide el permiso send, y pasas la revision que respondió la comprobación.

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);
}

El dueño del workspace también tiene que haber aceptado la política de uso aceptable. Hasta entonces, iniciar, testStep y una edición que aumenta lo que envía una campaña en curso responden 403 AUP_REQUIRED. La primera guía explica qué hacer.

Si la campaña cambió después de la comprobación, responde 409 campaignChanged y no empieza nada. Si queda un bloqueo, responde 409 campaignNotReady, con los bloqueos en details. Cuando el único bloqueo es campaign_limit, responde 402 PLAN_GATE.

Pasas a ser quien aprobó la campaña, y sus correos salen a tu nombre. Si pierdes el permiso send, o se revoca la clave, la campaña se pausa como approval_lapsed. Un dueño puede limitar una clave a ciertos buzones, campañas y flujos, y a un número de correos. La campaña tiene que caber en esos límites, o iniciar responde 403 con details.scope.

Iniciar una campaña en curso con su revisión actual no cambia nada. Iniciar una campaña en pausa la reanuda. Iniciar una terminada la reabre, y así envías a los leads que agregaste después de que terminó.

Con el permiso validate, iniciar también empieza a comprobar las direcciones de los leads a los que aún no se ha escrito y que no tienen veredicto, o tienen uno viejo. Pasa checkLeads: false para saltar solo las comprobaciones de ese inicio.

Después de iniciar, un cambio que solo quita algo no pide nada más. Cualquier otro pide el permiso send y te hace quien aprobó. Por ejemplo:

  • Un ritmo más rápido: dailyLimit, maxNewLeadsPerDay, rampStart o rampIncrement mayores, o un entryDelayMinutes menor.
  • Un cambio en schedule, textOnly o unsubscribeMode, el seguimiento de aperturas o clics encendido, o stopOnReply apagado.
  • Un paso nuevo, o el texto, la espera o el orden de un paso cambiados.
  • Un buzón agregado a la lista.
  • Una dirección nueva en CC o CCO, o una que pasó de CCO a CC.
  • Agregar leads o vincular un segmento.
  • Una versión nueva de un paso, un nombre, un mensaje o un peso cambiados, una versión en pausa reanudada o un ganador elegido.
  • Encender sendToRisky o sendToUnknown.

Estos solo quitan algo, así que mantienen la aprobación y no piden send: un nombre nuevo; un dailyLimit, rampStart, rampIncrement o límite de leads nuevos menores, o poner un límite; una espera antes del primer correo mayor; stopOnReply encendido; el seguimiento, sendToRisky o sendToUnknown apagados; newLeadsFirst o continuous cambiados, hacia cualquier lado; una dirección quitada de CC o CCO; un paso, un buzón, leads o un segmento quitados; una versión pausada o archivada.

Pausa, reanuda, mira cómo termina y elimina

El status de una campaña es draft, active, paused o completed.

pause detiene ahora una campaña en curso. Los correos que no han empezado a salir se cancelan, y sus leads reciben esos pasos de nuevo cuando se reanude. Los ajustes, los pasos y los leads se quedan. Un borrador o una campaña terminada responde 409 campaignNotRunning. Para reanudarla, inicia de nuevo, como arriba. Pide operate.

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

Una campaña en curso también puede pausarse sola. pausedReason dice el motivo, y es nulo cuando la pausó una persona:

no_mailboxes
No queda ningún buzón conectado en su lista.
no_confirmed_mailboxes
Todos los buzones de su lista tienen una dirección que nadie confirmó. El dueño del workspace confirma una en la consola.
domain_failing
Todos los buzones de su lista envían desde un dominio que falla en sus registros.
approval_lapsed
Quien la inició ya no puede enviar, o la campaña cambió de una forma que nadie aprobó.
guardrail
Al menos 50 correos salieron en 7 días, y 5% rebotaron o 0,1% se denunciaron como spam. Después de esta pausa, la cuenta empieza de cero.
list_quality
La parte de leads con dirección que se sabe inválida llegó a 4%. listGateBreach trae los conteos.
staff
Nuestro equipo la detuvo a mano. staffPauseReason dice el motivo.
workspace_closing
El dueño del workspace pidió eliminarlo. Iniciar se rechaza hasta que cancele la solicitud.

Resuelve la causa e inicia de nuevo.

Una campaña de secuencia termina cuando todos los leads terminan. Con continuous encendido, sigue en marcha y espera los leads que agregues. Una campaña sin ningún lead todavía también espera, sea del tipo que sea, y waitingForLeadsSince dice desde cuándo. Un envío único no puede ser continuo: termina cuando sus leads terminan.

campaigns.remove elimina una campaña con sus pasos, sus leads y su lista de buzones. Pide operate y no se puede deshacer. Los contactos se quedan, y quien se dio de baja por la campaña sigue dado de baja. Los correos que ya envió siguen en la lista de envíos, con campaignId en null.

listLog lee lo que le pasó a una campaña, de lo más nuevo a lo más viejo: lo que cambiaron personas y claves, y lo que la campaña hizo sola, como una pausa y su motivo, un final, un lead que dejó fuera o una copia que quitó. Guarda 90 días.

Pásale kinds a listLog para leer un solo grupo: changes (ediciones, pasos, buzones, leads agregados o quitados, la pausa de un lead y segmentos vinculados), status (inicios, pausas, finales y espera de leads) o leads (un lead dejado fuera o movido y una copia quitada). Sin él, llega todo el historial.

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

Sigue un lead

getLead dice qué hace la campaña a continuación con un lead, en next.state: due (la espera del lead terminó; los horarios, límites y pausas de la campaña siguen aplicando), waits o ended. Pide el permiso 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 });

Cuando un lead está en waits, why es uno de estos:

step_on_its_way
Un correo está en camino.
step_wait
El siguiente paso espera sus días después del anterior.
entry_delay
Un lead nuevo espera el tiempo definido antes del primer correo de la campaña.
away
Un mensaje de fuera de la oficina dijo que la persona vuelve más tarde.
held
Una persona pausó este lead.
new_leads_cap
Se acabaron los primeros correos de hoy. Empieza mañana.
address_check
Su dirección espera una comprobación, o que la campaña acepte una dirección riesgosa o sin conclusión.
mailbox_busy
El buzón con el que habla no puede enviar ahora.

Cuando un lead está en ended, why es uno de estos:

sequence_done
Recibió todos los pasos que podía.
replied
Respondió, y la campaña se detiene cuando hay respuesta.
replied_elsewhere
La persona respondió a otra campaña del workspace, y esta campaña se detiene cuando hay respuesta. El repliedElsewhere del lead dice cuál.
suppressed
Su dirección está en la lista de supresión, o se dio de baja.
invalid
Un servicio de verificación o tu archivo de contactos dijo que su dirección no existe.
failed
No se pudo enviar el siguiente paso, así que este lead se detuvo aquí.

La respuesta de una persona también termina sus leads en las otras campañas del workspace, a menos que el dueño lo haya apagado en Configuración. Viene encendido por defecto. Alcanza solo a las campañas que existen cuando llega la respuesta, así que una campaña que crees después no se ve afectada, y nunca termina un lead cuya campaña sigue escribiendo después de una respuesta. Un mensaje de fuera de la oficina, una respuesta que pide darse de baja y la respuesta de una copia (CC o CCO) no hacen esto. Los resultados del propio lead no cuentan esa respuesta, y su repliedAt sigue en null. listLog muestra una línea lead_replied_elsewhere por cada lead que terminó.

holdLead pausa un lead: no sale nada más hacia él desde esta campaña hasta until, una fecha y hora con su desfase horario que todavía esté por delante (una pasada responde 400 leadHoldEndPassed). Sin until, dura hasta campaigns.releaseLead. Un correo ya preparado para él que no ha empezado a salir se cancela. Esta pausa es aparte de la espera por un mensaje de fuera de la oficina, y el paso espera hasta que termine la más larga de las dos. Pide operate y mantiene la aprobación, porque no cambia nada de lo que envía la campaña.

campaigns.releaseLead termina la pausa de un lead: su siguiente paso puede salir enseguida, si la espera del propio paso ya pasó. La espera por un mensaje de fuera de la oficina sigue. Un lead que nadie pausó queda como estaba. Pide operate.

Un lead puede esperar la verificación de su dirección aunque tú sepas que recibe correos. Solo una persona puede decirlo: el sendAnyway del lead, que devuelven listLeads y getLead, muestra quién lo hizo y qué pasó por alto, y ninguna clave puede ponerlo. Para pedírselo a una persona, crea una solicitud del tipo send_lead_anyway, el campaignId y el contactId del lead, y explica en note por qué esperas que la dirección reciba correos. La persona abre el lead en la consola y lo envía de todos modos, o lo rechaza. La solicitud pasa entonces a completed o cancelled. Responde 409 para una dirección en la lista de supresión, una dirección que se halló inválida, una cuya verificación sigue en curso y una a la que la campaña ya le escribe. Nadie puede enviar por encima de las dos primeras.

Prueba dos versiones de un paso

addStepVariant agrega otra versión del mensaje de un paso. El texto del propio paso es el original, y un paso tiene hasta 4 versiones además de él, incluidas las pausadas. Archiva una para hacer espacio. Cada lead sortea una versión del paso, una sola vez, en proporción a los pesos, y se queda con ella: el mismo lead siempre recibe la misma. El peso del original es el weight del paso, y el de una versión nueva es 50, si no pasas uno.

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,
});

En un paso que responde en la misma conversación, una versión cambia solo el mensaje. Un asunto en ella responde 400 variantSubjectThreading. Una versión en pausa no se sortea, y si todas están en pausa se envía el original. Un paso guarda hasta 20 versiones archivadas para sus resultados.

campaigns.updateStepVariant cambia el nombre, el texto o el peso de una versión, o la pone en pausa con paused: true y la reanuda con paused: false. Envía solo lo que cambia. Reanudar pide un mensaje en el cuerpo. Un lead que ya sorteó una versión se queda con ella mientras pueda enviar, y sortea de nuevo cuando la versión se pausa. Cualquier cambio que no sea solo una pausa aumenta lo que envía la campaña, así que en una campaña en curso pide send, como dice la sección sobre iniciar. campaigns.removeStepVariant archiva una versión: sus números quedan en los resultados y su nombre vuelve a estar libre. Los leads que la sortearon sortean de nuevo entre las otras, si aún tienen un paso por enviar. No se puede recuperar: agrega una nueva. Las dos piden 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 elige un ganador por ti. pickStepWinner recibe el ID de una versión, o original. El mensaje elegido pasa a ser el del paso, el paso vuelve a valer para todos los leads y todas las versiones se archivan. Sus números quedan en los resultados. La consola dice que aún es pronto para concluir hasta que una versión haya enviado correo a 100 personas.

Una versión nueva, un nombre, un mensaje o un peso cambiados, una versión en pausa reanudada y un ganador aumentan lo que envía una campaña en curso, así que piden el permiso send, como dice la sección sobre iniciar. Pausar o archivar una versión no lo pide.

Lee los resultados

get devuelve la campaña con su engagement, contado en personas, no en correos: emailed, opened, clicked y replied. Un lead que abrió cinco veces cuenta una.

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

Solo cuenta lo que hizo una persona. Quedan fuera un escáner de correo, un proxy de imágenes, cualquier cosa demasiado pronto después de que salió el correo y varios enlaces seguidos desde la misma red en pocos segundos. openedByMachinesOnly cuenta los leads cuyos correos solo abrieron máquinas, lo que explica por qué opened puede verse bajo.

Una apertura es la imagen del correo cargándose, así que quien lee con las imágenes apagadas nunca cuenta. opened es un piso, no una tasa de lectura.

Las aperturas y los clics solo cuentan mientras la campaña los registra y uno de los dominios de seguimiento del workspace está en uso. Los dos vienen apagados. Hasta entonces, un 0 significa que no se midió nada.

steps repite los conteos por paso, sin respuestas, porque una respuesta es de la campaña y no de un paso. Cada paso con versiones también lista los conteos de cada una. links lista las páginas a las que llegaron las personas con sus clics, como máximo 10, en los últimos 90 días.

Mira qué dominio de seguimiento está en uso

Las aperturas y los clics se miden a través de un dominio de seguimiento: un nombre como t.acme.com con un registro CNAME que apunta a nuestros servidores. trackingDomains.list lista los dominios de seguimiento del workspace, del más antiguo al más nuevo. Pide read. Cada uno trae su hostname, el target al que apunta su registro, un 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 es pending hasta que se ve el registro, verified cuando apunta al destino y failing cuando el registro de un dominio verificado ya no está o apunta a otra parte. Los correos de una campaña llevan la imagen de seguimiento y los enlaces con seguimiento solo mientras un dominio está en uso (inUse): está verificado, nuestro lado del destino responde (targetResolves no es false) y vimos que nuestro lado sirve el nombre por HTTPS (httpsOkAt tiene valor). Esa última parte la preparamos nosotros, nunca tú. Mientras ningún dominio esté en uso, los correos salen sin la imagen y con los enlaces como se escribieron, y un 0 en los resultados significa que no se midió nada. Cada correo usa el dominio de seguimiento que cuelga del dominio desde el que se envía (t.acme.io para un correo de [email protected]), o el más antiguo en uso cuando no hay ninguno.

trackingDomains.check mira el registro de un dominio ahora, sin esperar a la comprobación de cada hora, y responde el dominio tal como queda. lastCheck dice qué respondió el DNS: verified, no_record, wrong_target (con observed, adonde lleva) o dns_unanswered. Un dominio verificado deja de usarse solo cuando el DNS responde que el registro ya no está o apunta a otra parte, nunca porque el DNS no respondió. Cuando el registro se verifica, comprobamos en minutos que nuestro lado sirve el nombre por HTTPS, y luego cada hora. Pide operate.

Un workspace puede verificar como máximo 10 nombres de host nuevos en 30 días. Pasado eso, una comprobación que llegaría a verificar responde 429 hasta que el más antiguo salga de la ventana, y no se verifica nada. La comprobación también cuenta en el límite del workspace para las comprobaciones de buzón, por minuto y por hora, y pasado ese límite responde 429 con Retry-After.

Una clave no puede agregar ni quitar un dominio de seguimiento: eso pide manage, que una clave nunca tiene, así que lo hace una persona en Configuración. Un workspace tiene hasta 10. Mientras un dominio no está verificado, dnsHost nombra al proveedor de DNS que tiene su zona, o es null cuando el DNS no respondió a tiempo.

Eventos que genera una campaña

Un webhook o events.list recibe estos cuando una campaña hace algo. La guía de webhooks muestra cómo recibirlos.

campaign.reply_received
Alguien respondió a un correo de campaña, o su servidor respondió por esa persona.
campaign.paused
Se pausó una campaña en curso. El evento dice por qué, salvo que la pausara una persona o una clave.
campaign.finished
Todos los leads recibieron la secuencia entera, así que la campaña terminó.
campaign.waiting_for_leads
Una campaña en curso se quedó sin nadie a quien escribir.

campaign.waiting_for_leads se dispara una vez cada vez que una campaña pasa a esperar. Su campo continuous es true cuando la campaña sigue en marcha esperando leads nuevos, y false cuando no tenía ningún lead.

Todo evento send.* trae un campaignId: el de la campaña que envió, o null para un correo que no era de una campaña. Los eventos de antes de este campo no lo traen, así que trátalo como opcional.

Lee la guía de webhooks