Campañas
Una campaña (broadcast) envía un mensaje a todos los contactos suscritos de una audiencia. SpacePost lo reparte en un correo personalizado por contacto, añade los enlaces de baja y los encabezados List-Unsubscribe, omite las direcciones suprimidas y hace el seguimiento de todo como con cualquier otro correo.
Ciclo de vida
Sección titulada «Ciclo de vida»| Estado | Significado |
|---|---|
draft |
Creada, editable, sin enviar. |
queued |
Se llamó a send; espera su hora de inicio (de inmediato, o scheduled_at). |
sending |
Se está repartiendo entre los contactos. |
sent |
Todos los correos se aceptaron para su entrega. sent_at está definido. |
failed |
El reparto se detuvo, por ejemplo porque se alcanzó una cuota o el equipo se pausó. |
canceled |
Cancelada antes de enviarse. |
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);const audienceId = "0192e1c4-0000-7000-8000-0000000000a1";
// 1. Create a draftconst { data: draft, error } = await spacepost.broadcasts.create({ audienceId, name: "October newsletter", from: "Acme <news@acme.com>", replyTo: "support@acme.com", subject: "What's new at Acme, {{{FIRST_NAME|friend}}}", previewText: "Three things we shipped this month", html: ` <h1>Hi {{{FIRST_NAME|there}}},</h1> <p>Here is what is new.</p> <p><a href="{{{SPACEPOST_UNSUBSCRIBE_URL}}}">Unsubscribe</a></p> `,});if (error) throw new Error(error.message);
// 2. Send it now, or schedule itconst { error: sendError } = await spacepost.broadcasts.send(draft.id, { scheduledAt: "in 1 hour" });if (sendError) throw new Error(sendError.message);
// 3. Check progressconst { data: broadcast } = await spacepost.broadcasts.get(draft.id);console.log(broadcast?.status); // "queued" | "sending" | "sent" | "failed"Crear una campaña
Sección titulada «Crear una campaña»POST /broadcasts crea un borrador y devuelve { "id": "..." }.
audience_idstring (uuid)obligatorioLa audiencia a la que enviar.
fromstringobligatorioRemitente en un dominio verificado.
subjectstringobligatorioDe 1 a 998 caracteres. Se admiten marcadores.
htmlstringopcionalCuerpo en HTML. Indica html, text o ambos antes de enviar.
textstringopcionalCuerpo en texto plano.
reply_tostring | string[]opcionalHasta 50 direcciones.
preview_textstringopcionalHasta 200 caracteres. Se muestra después del asunto en las bandejas de entrada; se inserta como preheader oculto.
namestringopcionalHasta 100 caracteres. Una etiqueta interna.
Actualizar y eliminar
Sección titulada «Actualizar y eliminar»PATCH /broadcasts/{id} acepta los mismos campos (todos opcionales) y funciona solo con borradores. DELETE /broadcasts/{id} funciona con borradores y con campañas programadas para el futuro.
# nowcurl -X POST https://api.spacepost.dev/broadcasts/$ID/send -H "Authorization: Bearer $SPACEPOST_API_KEY"
# scheduled (ISO or natural language, up to 30 days ahead)curl -X POST https://api.spacepost.dev/broadcasts/$ID/send \ -H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \ -d '{ "scheduled_at": "2026-11-01T09:00:00Z" }'await spacepost.broadcasts.send(id); // nowawait spacepost.broadcasts.send(id, { scheduledAt: "in 1 hour" }); // laterSolo se pueden enviar los borradores con un cuerpo html o text, desde un dominio verificado. La respuesta es { "id": "..." }.
Comprobaciones previas al envío
Sección titulada «Comprobaciones previas al envío»Antes de enviar, GET /broadcasts/{id}/readiness devuelve una lista de comprobación de entregabilidad (el panel muestra la misma lista en el editor). Es una extensión de SpacePost y no forma parte de la API de Resend.
{ "ready": true, "checks": [{ "id": "html_size", "level": "warn", "title": "Gmail will clip this message", "detail": "..." }] }| Comprobación | Nivel | Cuándo |
|---|---|---|
sender_domain |
error | El dominio de from falta o no está verificado. |
dmarc |
warn | No hay registro v=DMARC1 en _dmarc.<domain>, o la consulta falló. p=none pasa. |
subject |
error / warn | Error si está vacío. Advertencia por MAYÚSCULAS, !!! o $$$, o por más de 90 caracteres. |
html_size |
warn | HTML de más de 102 KB, que Gmail recorta. |
images |
warn | Solo imágenes, o cuatro o más imágenes con poco texto. |
shorteners |
warn | Enlaces a través de bit.ly, tinyurl.com, t.co y similares. |
spam_phrases |
warn | Expresiones típicas de spam en el asunto o el cuerpo. |
unsubscribe |
ok | Se añade automáticamente un pie de baja cuando tu cuerpo no tiene marcador, por lo que esto nunca bloquea. |
audience |
error | No hay contactos suscritos. |
role_addresses |
warn | Más del 20 % de los contactos son direcciones como info@ o admin@. |
ready es false cuando alguna comprobación tiene nivel error. El envío falla entonces con un validation_error (422) que lista lo que hay que corregir. Las advertencias nunca bloquean un envío.
Los correos de campañas también llevan los encabezados List-Id y Feedback-ID (<broadcast id>:<team id>:broadcast:spacepost) para que Gmail Postmaster Tools pueda atribuir las quejas a una campaña. Los correos transaccionales no los llevan.
Qué recibe un destinatario
Sección titulada «Qué recibe un destinatario»- La personalización se aplica a
subject,htmlytext(Personalización). - Baja: si tu cuerpo no tiene el marcador
{{{SPACEPOST_UNSUBSCRIBE_URL}}}/{{{RESEND_UNSUBSCRIBE_URL}}}, se añade automáticamente un pie de baja. Los encabezadosList-UnsubscribeyList-Unsubscribe-Post: List-Unsubscribe=One-Clickse añaden siempre. - El Texto de vista previa se convierte en un preheader oculto al principio del HTML.
- Los contactos dados de baja y las direcciones de tu lista de supresión se omiten.
Cuota y límites
Sección titulada «Cuota y límites»Cada destinatario cuenta como un correo para la cuota de tu plan, y el sandbox de 100 por día se aplica a las cuentas nuevas. Si la cuota se agota a mitad del envío, la campaña termina como failed; los correos ya creados se entregan igualmente. El reparto se puede reanudar si un worker se reinicia.
Cada correo de una campaña es un correo normal: aparece en GET /emails, lleva broadcast_id en los payloads de webhook y puede rebotar o abrirse.