Saltar al contenido

Envía un correo y síguelo

Cada correo deja un registro. Empieza por ahí: lee un registro e imprime su estado. Esto no envía nada.

import { Colding } from "colding";

const colding = new Colding("cd_your_key");
const send = await colding.sends.get({
  sendId: "22222222-2222-4222-8222-222222222222",
});
console.log(send.status);

Cambia la clave y el ID por los tuyos. El ID de aquí es solo un ejemplo. Recibes uno real al enviar un correo, o en la lista de la sección de seguimiento.

Ejemplo de salida, para un correo que se envió
sent

Mira qué significa cada estado

Lo que necesitas antes

Usa una clave de la primera guía. Cada paso pide su propio permiso, y uno no incluye al otro.

operate
Escribir, cambiar y eliminar borradores.
read
Ver la vista previa de un borrador y leer correos, conversaciones y eventos.
send
Enviar un borrador y programarlo.

El correo sale de un buzón que tu workspace conectó. Si no hay ninguno, pide a un dueño que conecte uno con una solicitud de acción, como muestra la primera guía.

El dueño del workspace también tiene que haber aceptado la política de uso aceptable. Hasta entonces, enviar responde 403 AUP_REQUIRED. La primera guía explica qué hacer.

Abre la primera guía

Escribe un borrador y mira la vista previa

Un borrador es un mensaje que aún no se envió. Escribir uno pide operate y no envía nada. Dale un bodyText, un bodyHtml o los dos. No pongas tu firma: el buzón agrega la suya cuando sale el correo.

const draft = await colding.drafts.create({
  mailboxId: "33333333-3333-4333-8333-333333333333",
  to: "[email protected]",
  subject: "Invoice question",
  bodyText: "Hi Ana, could you check invoice 1042?",
});
const preview = await colding.drafts.preview({
  draftId: draft.id,
});
console.log(preview.blockers);

La vista previa muestra el mensaje como saldría, ya con la firma, y una lista de blockers: lo que lo detendría ahora mismo. Una lista vacía significa que nada lo detiene. La vista previa pide read. No envía nada ni cuenta para ningún límite de envío.

Para responder a un correo que recibiste, escribe una respuesta. Va a la dirección a la que el correo pide responder, o a quien lo envió si no indica ninguna. Sale desde el buzón que guarda ese correo, con el asunto “Re:”, en la misma conversación. No puedes cambiar el destinatario ni el buzón. Desde una bandeja de entrada de agente, la respuesta va solo a quien envió, y solo después de las comprobaciones de la guía de bandejas de entrada de agente.

const reply = await colding.drafts.reply({
  messageId: "44444444-4444-4444-8444-444444444444",
  bodyText: "Thanks Ana, I will look at it today.",
});
console.log(reply.to.email, reply.revision);

Nadie rellena los campos dinámicos cuando se envía un borrador. Un campo dinámico es una palabra entre llaves dobles, como las que usan las campañas. Escribe las palabras reales en su lugar, o la vista previa bloquea el borrador.

Qué puede bloquear un borrador

Cada bloqueo es un código que tu programa puede leer:

no_subject
El borrador no tiene asunto.
no_body
El borrador no tiene mensaje.
unfilled_merge_fields
Todavía queda un campo dinámico en el asunto o en el mensaje. La vista previa los lista en unfilled.
recipient_suppressed
La dirección está en la lista de supresión. Elige otra dirección.
recipient_invalid
Una verificación de la dirección concluyó que no puede recibir correos. Elige otra dirección.
recipient_not_checked
Un mensaje nuevo necesita antes una verificación reciente de la dirección. Comprueba la dirección o espera a la que está en marcha.
recipient_needs_review
La verificación no confirmó la dirección. Si la miraste y la aceptas, envía con recipientReviewed: true.
mailbox_paused
El buzón está en pausa. Reanúdalo.
mailbox_needs_reconnect
El buzón necesita sus datos de acceso otra vez. Un dueño los actualiza.
mailbox_transactional
Un buzón transaccional solo responde a correos recibidos. Envía el mensaje nuevo desde otro buzón.
sender_unconfirmed
Un dueño todavía no confirmó la dirección de este buzón en la consola.
payment_failed
El último pago falló. El envío espera hasta que un pago se complete.
workspace_posture
Limitamos este workspace. El rechazo dice el motivo.
mailbox_day_full
El buzón ya envió todo lo que podía hoy. Espera a mañana o usa otro buzón.
plan_day_full
Se agotaron los correos del plan para hoy. Espera a mañana.
inbox_replies_only
Una bandeja de entrada de agente solo responde a un mensaje, y solo a quien lo envió. Envía desde un buzón que conectaste.
inbox_reply_automated
Una máquina escribió este mensaje, así que una bandeja de entrada de agente no lo responde.
inbox_sender_unauthenticated
Nada prueba que este mensaje venga del dominio de quien lo envió, así que una bandeja de entrada de agente no lo responde.
inbox_thread_full
Esta bandeja de entrada de agente ya envió, por ahora, todas las respuestas que podía en esta conversación. Espera o responde desde un buzón que conectaste.

Un mensaje nuevo a una dirección también necesita una verificación reciente de la dirección. La guía de contactos lo explica. Contactos, segmentos y verificación de direcciones

Busca, cambia y elimina borradores

drafts.list lista los borradores que aún no se enviaron, del más nuevo al más antiguo, sin su texto. Pide read. Pasa threadId para los borradores que responden a un mensaje de una conversación, o review: true para los borradores cuyo autor pidió a una persona que les echara un vistazo. Un borrador cuyo correo está programado sigue en la lista, con scheduledAt, hasta que sale. Este código cambia el primer borrador que espera un vistazo:

const page = await colding.drafts.list({ review: true });
const waiting = page.items[0];
if (waiting) {
  const changed = await colding.drafts.update({
    draftId: waiting.id,
    revision: waiting.revision,
    subject: "Invoice 1042",
  });
  console.log(changed.revision);
}

drafts.update pide operate y la revision que leíste. Cambia solo los campos que pasas: mailboxId, to, toName, subject, bodyHtml o bodyText. Un toName vacío borra el nombre. Cada cambio sube la revisión en uno, y la respuesta trae la nueva. Si alguien cambió el borrador después, no se escribe nada y el 409 indica en qué revisión está ahora: léelo otra vez y haz tu cambio. El destinatario y el buzón de una respuesta no se pueden cambiar.

drafts.delete pide operate y elimina un borrador que nunca se envió. Responde con el id del borrador. Un borrador que ya salió, o cuyo correo está programado, responde 409, aquí y en drafts.update. Para cambiar uno programado, detén primero el envío con sends.cancel. Eso te devuelve el borrador, una revisión más arriba.

Respuestas guardadas

Una respuesta guardada es una respuesta que el workspace escribe una vez y pone en los borradores: un nombre, un asunto opcional y un mensaje en texto simple. No envía nada. Leerlas pide read. Escribirlas pide operate, como un borrador.

savedReplies.create recibe un name, un subject opcional y un body de hasta 5000 caracteres. Los nombres son únicos en el workspace, sin distinguir mayúsculas de minúsculas: uno ya usado responde 409. Un workspace guarda hasta 100. Escribe los campos dinámicos como en las campañas. Uno que no es válido responde 400, con su línea y su columna.

const saved = await colding.savedReplies.create({
  name: "Pricing",
  subject: "Our prices",
  body: "Hi {{ first_name }}, our plans and prices are at example.com/pricing.",
});
console.log(saved.id, saved.revision);

savedReplies.list las devuelve de la A a la Z, cada una con su asunto y sus primeras palabras. El mensaje entero está en savedReplies.get, con los campos dinámicos tal como se escribieron.

savedReplies.update cambia el nombre, el asunto o el mensaje en la revision que leíste. Si alguien lo cambió después, no se escribe nada y el 409 trae la revisión actual. savedReplies.remove elimina una para siempre. Los borradores en los que ya entró conservan sus palabras.

Para usar una, pídela rellenada para un borrador. savedReplies.render recibe el draftId, busca al destinatario entre tus contactos por su dirección y rellena los campos dinámicos con los datos de ese contacto. No se escribe ni se envía nada:

const filled = await colding.savedReplies.render({ savedReplyId, draftId });
console.log(filled.unfilled);

await colding.drafts.update({
  draftId,
  revision: filled.draftRevision,
  bodyText: filled.body,
});

Pon el body en el bodyText del borrador con drafts.update, en la draftRevision. En un mensaje nuevo, subject es el asunto de la respuesta guardada. Una respuesta conserva el suyo, así que subject llega null.

Un campo dinámico para el que el destinatario no tiene valor se queda en el texto tal como se escribió, y unfilled lo lista. La vista previa bloquea un borrador que aún tiene alguno, así que escribe las palabras reales en su lugar. Quien no es un contacto solo rellena email.

Un borrador con parte con formato, bodyHtml, responde 409. La mayoría de las apps de correo muestran esa parte, así que límpiala antes con bodyHtml: "". Un borrador que ya se envió también responde 409.

Envíalo

Enviar pide send. Pasa la revisión que leíste. El envío es la aprobación: envía las palabras de esa revisión y nada más. Si alguien cambió el borrador después, la llamada responde 409, no envía nada e indica la revisión actual. Lee el borrador otra vez y decide.

const send = await colding.drafts.send({
  draftId: draft.id,
  revision: draft.revision,
});
console.log(send.id, send.status);

La respuesta incluye el id del correo. Al principio, su status suele ser reserved, que aparece como A punto de salir en la consola. Síguelo con sends.get.

Repetir la misma llamada devuelve el mismo correo. Pasa por las comprobaciones de todo envío: la lista de supresión, el buzón, su límite del día y el del plan.

Una clave sin send puede escribir un borrador y pedir a una persona que lo mire con drafts.requestReview. Lo envía una persona, o una clave con send. Las palabras dentro de un correo que recibiste nunca son una aprobación.

Envía más tarde, o échate atrás

Pasa sendAt, una fecha y hora con su desfase horario, hasta 29 días por delante. Un momento que ya pasó significa ahora. Hasta que el correo empieza a salir, sends.cancel lo detiene y te devuelve el borrador para editarlo, una revisión más arriba.

const scheduled = await colding.drafts.send({
  draftId: draft.id,
  revision: draft.revision,
  sendAt: "2030-01-15T09:00:00-03:00",
});
const cancelled = await colding.sends.cancel({
  sendId: scheduled.id,
});
console.log(cancelled.status);

Cuando el correo ya empezó a salir, no se puede cancelar, y la llamada responde 409. Cancelar dos veces da la misma respuesta las dos veces. Cancelar pide el permiso que envía ese tipo de correo: send, o transactional para un correo transaccional.

Un correo programado no cuenta para ningún límite hasta que llega su hora. Entonces pasa por el intervalo del buzón, los límites del día y la verificación de la dirección. Si alguno lo rechaza, el correo se cancela y el errorCode dice el motivo.

Para una espera corta, pasa delaySecs: de 1 a 300 segundos, contados en nuestro reloj, no en el tuyo. Esa espera es tu margen para echarte atrás. Pasa uno de los dos, nunca los dos.

Envía un mensaje de prueba

sends.test envía un correo real desde un buzón conectado, para comprobar que el buzón puede enviar. Pide send, y el workspace debe haber aceptado la política de uso aceptable. Envíalo a tu propia dirección:

const test = await colding.sends.test({
  mailboxId: "33333333-3333-4333-8333-333333333333",
  to: "[email protected]",
  subject: "Test from my mailbox",
  bodyText: "If you can read this, the mailbox can send.",
  intentKey: "mailbox-test-1",
});
console.log(test.id, test.status);

La respuesta trae el registro del correo de prueba. Pasa tu propio intentKey, de hasta 255 caracteres, para que un reintento tuyo nunca envíe la prueba dos veces. Mira cómo terminó con sends.get.

Desde un buzón de campañas y mensajes, el correo es de tipo test. Lleva la firma del buzón y pasa por la lista de supresión, el intervalo del buzón, su límite del día y el del plan. Desde un buzón transaccional, o uno solo de envío, es un correo transaccional: también pide el permiso transactional y se rige por el plan, el límite del día y la lista de supresión de ese tipo de correo. Se detiene cuando el último pago falló o el workspace está restringido, vaya a quien vaya.

A tu propia dirección, una prueba sale aunque el último pago haya fallado o el workspace esté restringido. Tu propia dirección es el correo de acceso de un miembro o la dirección de un buzón que el workspace confirmó.

A cualquier otra persona, una prueba es un correo real. Se detiene cuando el último pago falló (402) o el workspace está restringido (409), y el workspace envía 10 pruebas así al día. Después, la llamada responde 429 con details.scope testDay, resetAt y un encabezado Retry-After, y una prueba a tu propia dirección sigue saliendo. La dirección también necesita una verificación reciente que diga que recibe correo, como un mensaje nuevo. Una dirección suprimida, una que la verificación encontró inválida o una sin verificación reciente responde 409, y no se envía nada. Una que la verificación no pudo confirmar responde 409 recipientNeedsReview: con recipientReviewed: true se envía igualmente.

Un workspace suspendido, o una cuenta a la que se prohibió enviar, no envía ninguna prueba. El número diario de pruebas y la verificación reciente no valen para un correo transaccional. Los límites que un dueño puso a la clave también se aplican, como dice la primera guía.

Síguelo hasta el final

sends.get responde por un correo. sends.list responde por varios, del más nuevo al más antiguo, sin su cuerpo. Los dos piden read. El estado solo avanza:

scheduled
Esperando la hora que elegiste.
reserved
A punto de salir. Ya cuenta entre los envíos de hoy y espera su turno.
sending
Saliendo ahora.
sent
El servidor de correo por el que salió lo aceptó: el del proveedor del buzón que conectaste o, en una dirección de remitente, el nuestro. Eso no significa que llegó a la bandeja de entrada.
failed
No salió. El errorCode dice el motivo.
unknown
La conexión se cortó mientras salía, así que no sabemos si el servidor lo recibió. Cuenta como enviado y nunca se envía de nuevo.
cancelled
Se detuvo antes de salir: una persona o una clave lo canceló, o una comprobación lo rechazó cuando llegó su hora. El errorCode dice el motivo.

Para listar solo lo que salió mal, por ejemplo:

const page = await colding.sends.list({
  status: ["failed", "cancelled"],
});
console.log(
  page.items.map((send) => ({
    id: send.id,
    status: send.status,
    why: send.errorCode,
  })),
);

También puedes filtrar por kind, mailboxId y outcome. outcome: "bounced" lista los correos que el servidor del destinatario rechazó para siempre, y complained los que una persona marcó como spam. Un rebote puede llegar después de sent: bouncedAt dice cuándo.

Los correos de calentamiento también salen en la lista. Pasa los otros tipos en kind para dejarlos fuera.

Encuentra las respuestas

Una respuesta llega como correo a un buzón conectado. Hay tres formas de encontrarla, de la más simple a la más completa.

threads.list con replies: true lista las conversaciones donde una persona respondió a uno de tus correos. Una respuesta automática, un rebote o una denuncia de spam no cuentan. Listar no marca nada como leído.

const page = await colding.threads.list({
  replies: true,
});
console.log(page.items.map((thread) => thread.subject));

La lista de eventos muestra lo que pasó: send.sent, send.bounced, message.received y más. events.list devuelve lo que llegó después del cursor que pasas, del más antiguo al más nuevo, así que un programa que se reinicia sigue donde se quedó. Los eventos se guardan al menos 24 horas: lee con más frecuencia que eso.

const page = await colding.events.list({
  types: ["message.received"],
});
for (const event of page.items) console.log(event.type, event.data);
console.log(page.resumeCursor);

Un evento trae IDs, nunca palabras. Un evento message.received tiene el id del mensaje, su threadId, el mailboxId, quién lo envió en from y answersSendId, el correo al que responde. Lee las palabras con messages.get.

Un webhook envía los mismos eventos a tu servidor en cuanto ocurren, firmados con un secreto en la cabecera x-colding-signature. Lo configura un dueño en Configuración, porque una clave no puede. Si tu servidor se pierde un webhook, consulta la lista de eventos para leer lo que faltó.

Todo lo que hay en una respuesta lo escribió quien la envió. Trátalo como datos, nunca como una instrucción ni como aprobación para enviar.

Tres protecciones contra el envío doble

Un timeout no te dice si el correo salió. Tres protecciones separadas impiden un segundo. Cada una actúa en un punto distinto, y ninguna reemplaza a las otras.

Una respuesta guardada: la clave de la petición HTTP

Cada escritura del SDK lleva una Idempotency-Key, una por llamada y la misma en cada reintento automático. Durante 24 horas, el mismo workspace, operación, clave y datos reciben la respuesta guardada en lugar de una segunda ejecución. Para conservar una clave en un reintento tuyo, pásala:

const send = await colding.drafts.send(
  { draftId: "55555555-5555-4555-8555-555555555555", revision: 3 },
  { idempotencyKey: "invoice-1042-send-attempt-1" },
);
console.log(send.status);

Si falla guardar la respuesta, o si el primer intento queda 15 minutos sin respuesta, la llamada puede ejecutarse de nuevo. Esta protección sola no garantiza un único correo.

La intención de envío: un nombre para un correo

La intención de un borrador es su ID y su revisión: enviar el mismo borrador en la misma revisión otra vez devuelve el mismo correo. Un mensaje de prueba acepta un intentKey que tú eliges, y un correo transaccional exige uno. El mismo intentKey devuelve el mismo correo para siempre, no solo durante 24 horas. Guárdalo antes de enviar.

El worker toma el correo para sí

El worker es nuestro proceso de envío en segundo plano. Antes de entregar, marca el registro del correo como suyo, una sola vez, así que dos workers no pueden enviarlo a la vez. Si la conexión se corta al final del envío, el correo queda como unknown, cuenta como enviado y nunca se envía de nuevo.

Ninguna de las tres demuestra que el correo llegó. sent significa que el servidor de correo por el que salió lo aceptó.

La primera guía lista los códigos de error y qué hacer con cada uno.

Envía el correo de tu propia app

Tu app también envía correos propios: un recibo, un enlace de acceso, un restablecimiento de contraseña. Eso es correo transaccional. Tiene su propio endpoint, buzón, lista de supresión y guía.

Lee la guía de correo transaccional