Skip to content
Browse the docs

Integrating · Published 2026-09-23 · Updated 2026-10-02 · AgentiSend

Broadcasts

One message to one segment, drafted, optionally scheduled, snapshotted at send, with a delivery state for every member.

A broadcast is one message to the contacts in one segment. Draft it with POST /broadcasts — "Draft a broadcast to a segment. Content comes from a template version or an inline body." — then send it with POST /broadcasts/{id}/send, or give it a scheduled_at time and it sends itself. Every message carries one-click unsubscribe headers, and every member gets a recorded outcome.

Who it reaches

The audience is a segment, created with POST /segments from rules that all have to match (see how to create a segment of contacts). A contact is an address someone gave you for this; there is no list import attached to broadcasts, and nothing here makes writing to strangers easier. At send time only contacts whose status is active receive the message. An unsubscribed or bounced contact is skipped, and so is an address on the suppression list. Audience covers contacts and segments in the console.

Every broadcast shows the reader how to stop getting it and who sent it. Gmail and Yahoo expect a visible unsubscribe link in bulk mail, and US law (CAN-SPAM) asks for the sender's postal address.

  • Postal address. Set it once for the account: the field in the new broadcast form, or PATCH /account with {"postal_address": "1 Main Street, Springfield, IL 62701"}. A broadcast is not sent until the account has one; the send is refused with a message that says where to set it.
  • Your own link. Put {{unsubscribe_url}} where the link should go, for example [Stop these emails]({{unsubscribe_url}}) in markdown or <a href="{{unsubscribe_url}}"> in HTML. Each contact gets their own signed link. {{{AGENTISEND_UNSUBSCRIBE_URL}}} works too.
  • The footer. If the body does not already show the link or the address, a short footer with both is added at the end, in the HTML and in the plain-text part. One click on the link unsubscribes that contact; later broadcasts skip them.

The draft

A draft needs a name, a from_address on a domain that has finished verification, a segment_id, and content from one of two places:

  • template_id: the template's published version supplies the subject and body. A template with no published version is refused at send.
  • subject and content, inline, in the shape templates use: "content must be {"mode": "markdown", "source": "..."} (mode markdown or html), or {"mode": "block", "blocks": [...]}."

from_address may carry a display name, "Acme Updates <news@yourdomain.com>", and any address on a verified domain works, not only one fixed sender. An address that does not parse is refused when you save the draft, not when the send reaches the first contact.

Three optional fields shape what each contact receives. Each takes Resend's name, and the Node SDK's spelling in brackets works too:

  • reply_to (replyTo): one address or a list. Every message carries it as Reply-To, so answers go to a person rather than the sending address. Checked the way POST /emails checks it.
  • preview_text (previewText): the line an inbox shows after the subject, up to 500 characters. It sits hidden at the top of the HTML; readers see it in the inbox list, not in the message.
  • topic_id (topicId): a topic on your account. A contact who unsubscribed from that topic is skipped, and each message's one-click unsubscribe turns off that topic only, the same as a topic_id on POST /emails. A topic from another account is refused as not found.

PATCH /broadcasts/{id} takes the same three, and null clears one.

A segment or template that a draft, scheduled or failed broadcast still uses cannot be deleted. Archive the broadcast, or point it at another segment, first.

If sending is paused for every account during an incident, a broadcast that falls due waits and goes out once sending resumes. Contacts it already reached are not mailed again.

Placeholders such as {{first_name}} are filled per contact from their properties, and email is always available. PATCH /broadcasts/{id} — "Edit an unsent broadcast, or rename ANY one. Drafts and scheduled broadcasts are editable; a scheduled_at time schedules it and null returns it to draft. Sent content is immutable."

While it sends

Send now starts the list and answers while that work is still going, with the broadcast reading sending. A short list that finishes before the answer reads sent. The rest of a longer list goes out after the answer, so the request does not stay open for the whole list.

Cancel while it is still sending stops the rest of the list. Contacts already reached stay sent. The ones not yet reached are not mailed.

If the service restarts part-way through a list, sending continues for the contacts not yet reached. A contact already handed a message is not mailed a second time.

{
  "name": "September product notes",
  "from_address": "news@yourdomain.com",
  "segment_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "subject": "What changed in September",
  "preview_text": "Two new regions and a faster send path",
  "reply_to": "support@yourdomain.com",
  "content": { "mode": "markdown", "source": "Hi {{first_name}}, here is what shipped." },
  "scheduled_at": "2026-10-01T09:00:00Z"
}

Scheduling and cancelling

A schedule is optional. Setting scheduled_at moves the broadcast to scheduled, and it sends when that time arrives. POST /broadcasts/{id}/cancel — "Cancel a scheduled broadcast before it sends. It returns to draft, editable and re-schedulable." A broadcast that is already sending or sent cannot be cancelled; the refusal says which state it is in, because that mail has gone.

What send does

POST /broadcasts/{id}/send — "Send now. Content is SNAPSHOTTED: the segment is evaluated and every member rendered with their properties; the result is immutable." The segment is evaluated at that moment, not when you drafted, so a contact who joined since the draft is included and one who unsubscribed since is not. The reply carries sent and skipped. A send cannot run twice: a second request while it is sending or after it has sent is refused. A segment too large to send in one go is refused before anything goes out, and the broadcast is marked failed; split the segment and send each part.

curl -sS -X POST https://api.agentisend.com/broadcasts/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42/send \
  -H "Authorization: Bearer $AGENTISEND_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
200
{
  "broadcast": {
    "archived_at": "2026-09-04T09:14:00.000Z",
    "audience_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
    "content": {},
    "created_at": "2026-09-04T09:14:00.000Z",
    "from": "example",
    "from_address": "example",
    "html": "example",
    "id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
    "name": "example",
    "object": "broadcast",
    "preview_text": "example",
    "reply_to": [],
    "scheduled_at": "2026-09-04T09:14:00.000Z",
    "segment_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
    "sent_at": "2026-09-04T09:14:00.000Z",
    "sent_count": -9007199254740991,
    "status": "example",
    "subject": "example",
    "template_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
    "text": "example",
    "topic_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
    "updated_at": "2026-09-04T09:14:00.000Z"
  },
  "id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "sent": -9007199254740991,
  "skipped": -9007199254740991,
  "warnings": []
}
Response

Per-member results

GET /broadcasts/{id}/messages is "Per-member delivery state for this broadcast." Each row has the contact_id, the message_id when one was sent, and a state:

  • sent: accepted for delivery. Follow the message_id at GET /emails/{id}.
  • skipped: the contact is not active, or the address is suppressed.
  • skipped:topic_unsubscribed: the contact unsubscribed from the broadcast's topic.
  • plan_limit_reached: the plan inclusion ran out part way through.
  • missing_variable:first_name: the body wanted a property this contact does not have, named after the colon.
curl -sS -X GET https://api.agentisend.com/broadcasts/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42/messages \
  -H "Authorization: Bearer $AGENTISEND_API_KEY"
200
{
  "data": [],
  "has_more": false,
  "next_cursor": "example",
  "object": "list"
}
Response

A broadcast that reached nobody ends failed, not sent. Fill in what the rows name and send it again: contacts already sent are not sent twice, and skipped ones are tried again.

Unsubscribe

Each message carries the RFC 8058 List-Unsubscribe and List-Unsubscribe-Post headers for its contact; you do not add them. That is why the no_unsubscribe_bulk lint rule's fix reads "Send via POST /broadcasts, which adds RFC 8058 headers automatically." A one-click unsubscribe marks the contact unsubscribed and suppresses the address for marketing mail only, so receipts and password resets still reach them. To let people leave one kind of mail and keep the rest, give the broadcast a topic_id: its one-click link then turns off that topic only, and the contact keeps getting your other topics. See how to add an unsubscribe link.

Duplicate

POST /broadcasts/{id}/duplicate copies a broadcast into a new draft named "Copy of" and the original name, or the name you pass. The copy keeps the From, subject, content or template, segment, reply_to, preview_text and topic_id. It has no schedule, no sends and no per-member history, whether the original was a draft, scheduled or long sent. The console's broadcast page has a Duplicate button that does the same.

Archive

POST /broadcasts/{id}/archive — "Archive — reversible, any time, sent or draft." The same call restores it. GET /broadcasts lists "Every broadcast, newest first — archived included."

Next