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.
sent
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.
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
errorCodedice 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
errorCodedice 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.