Skip to content

Broadcasts

A broadcast sends one message to every subscribed contact of an audience. SpacePost fans it out into one personalized email per contact, adds unsubscribe links and the List-Unsubscribe headers, skips suppressed addresses and tracks everything like any other email.

Status Meaning
draft Created, editable, not sent.
queued send was called; waiting for its start time (immediately, or scheduled_at).
sending Being fanned out to contacts.
sent Every email has been accepted for delivery. sent_at is set.
failed Fan-out stopped, for example because a quota was reached or the team was paused.
canceled Canceled before sending.
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 creates a draft and returns { "id": "..." }.

audience_idstring (uuid)required

The audience to send to.

fromstringrequired

Sender on a verified domain.

subjectstringrequired

1 to 998 characters. Placeholders are supported.

htmlstringoptional

HTML body. Provide html, text or both before sending.

textstringoptional

Plain-text body.

reply_tostring | string[]optional

Up to 50 addresses.

preview_textstringoptional

Up to 200 characters. Shown after the subject in inboxes; inserted as a hidden preheader.

namestringoptional

Up to 100 characters. An internal label.

PATCH /broadcasts/{id} accepts the same fields (all optional) and works only on drafts. DELETE /broadcasts/{id} works on drafts and on broadcasts that are scheduled for the future.

Terminal window
# 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" }'

Only drafts with an html or text body can be sent, from a verified domain. The response is { "id": "..." }.

Before you send, GET /broadcasts/{id}/readiness returns a deliverability checklist (the dashboard shows the same list in the editor). It is a SpacePost extension and not part of Resend’s API.

{ "ready": true, "checks": [{ "id": "html_size", "level": "warn", "title": "Gmail will clip this message", "detail": "..." }] }
Check Level When
sender_domain error The from domain is missing or not verified.
dmarc warn No v=DMARC1 record at _dmarc.<domain>, or the lookup failed. p=none passes.
subject error / warn Error when empty. Warning for ALL CAPS, !!! or $$$, or more than 90 characters.
html_size warn HTML over 102 KB, which Gmail clips.
images warn Image-only, or four or more images with little text.
shorteners warn Links through bit.ly, tinyurl.com, t.co and similar.
spam_phrases warn Common spam wording in the subject or body.
unsubscribe ok An unsubscribe footer is added automatically when your body has no placeholder, so this never blocks.
audience error No subscribed contacts.
role_addresses warn More than 20% of contacts are addresses like info@ or admin@.

ready is false when any check has level error. Sending then fails with a validation_error (422) that lists what to fix. Warnings never block a send.

Broadcast emails also carry List-Id and Feedback-ID headers (<broadcast id>:<team id>:broadcast:spacepost) so Gmail Postmaster Tools can attribute complaints to a broadcast. Transactional emails do not.

  • Personalization is applied to subject, html and text (Personalization).
  • Unsubscribe: if your body has no {{{SPACEPOST_UNSUBSCRIBE_URL}}} / {{{RESEND_UNSUBSCRIBE_URL}}} placeholder, an unsubscribe footer is appended automatically. The List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click headers are always added.
  • Preview text becomes a hidden preheader at the top of the HTML.
  • Contacts that are unsubscribed, and addresses on your suppression list, are skipped.

Every recipient counts as one email against your plan quota, and the 100-per-day sandbox applies to new accounts. If the quota is exhausted mid-send the broadcast ends as failed; emails already created are still delivered. Fan-out is resumable if a worker restarts.

Each broadcast email is a normal email: it appears in GET /emails, carries broadcast_id in webhook payloads and can bounce or be opened.