Ir al contenido

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.

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.
broadcast.ts
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
const audienceId = "0192e1c4-0000-7000-8000-0000000000a1";
// 1. Create a draft
const { 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 it
const { error: sendError } = await spacepost.broadcasts.send(draft.id, { scheduledAt: "in 1 hour" });
if (sendError) throw new Error(sendError.message);
// 3. Check progress
const { data: broadcast } = await spacepost.broadcasts.get(draft.id);
console.log(broadcast?.status); // "queued" | "sending" | "sent" | "failed"

POST /broadcasts crea un borrador y devuelve { "id": "..." }.

audience_idstring (uuid)obligatorio

La audiencia a la que enviar.

fromstringobligatorio

Remitente en un dominio verificado.

subjectstringobligatorio

De 1 a 998 caracteres. Se admiten marcadores.

htmlstringopcional

Cuerpo en HTML. Indica html, text o ambos antes de enviar.

textstringopcional

Cuerpo en texto plano.

reply_tostring | string[]opcional

Hasta 50 direcciones.

preview_textstringopcional

Hasta 200 caracteres. Se muestra después del asunto en las bandejas de entrada; se inserta como preheader oculto.

namestringopcional

Hasta 100 caracteres. Una etiqueta interna.

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.

Ventana de terminal
# now
curl -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" }'

Solo se pueden enviar los borradores con un cuerpo html o text, desde un dominio verificado. La respuesta es { "id": "..." }.

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.

  • La personalización se aplica a subject, html y text (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 encabezados List-Unsubscribe y List-Unsubscribe-Post: List-Unsubscribe=One-Click se 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.

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.