Broadcasts
Broadcasts let you send a single email to a topic within an audience. A topic is required — this ensures every broadcast is intentionally targeted. Sending is handled by a background worker which handles queing and batch sending. All broadcast endpoints require a full-access API key.
Unsubscribe links
Every broadcast email sent through Sendex must include an unsubscribe link. Sendex handles this automatically — you control where the link appears by placing the {{unsubscribe_link}} placeholder in your html_body or text_body.
How it works
- Each recipient has a unique, signed unsubscribe token stored on their contact record.
- At send time, Sendex replaces every
{{unsubscribe_link}}occurrence with that recipient's personal unsubscribe URL. - When the recipient visits the link, their contact is marked as unsubscribed and a
contact.unsubscribedwebhook fires. - Unsubscribed contacts are skipped by all future broadcasts automatically.
Placement in HTML
Use the placeholder as the href of an anchor tag in your HTML template. The AI template builder does this automatically.
<a href="{{unsubscribe_link}}" style="color:inherit;text-decoration:underline;">
Unsubscribe
</a>Placement in plain text
For plain-text broadcasts, place the placeholder inline — it will be replaced with the raw URL.
To unsubscribe, visit: {{unsubscribe_link}}Automatic fallback
If your body does not contain {{unsubscribe_link}}, Sendex appends a default unsubscribe footer automatically — so the requirement is always satisfied regardless.
Create a broadcast
Creates a broadcast and immediately queues it for sending. If scheduled_at is a future timestamp the broadcast will be held until that time.
Request Body
audience_idstringrequiredID of the audience to send to.
fromstringrequiredSender address. Must belong to a verified domain on your account.
from_namestringoptionalSender display name. Recipients see "Name <email>" instead of just the address.
subjectstringrequiredEmail subject line.
text_bodystringoptionalPlain-text email body. At least one of text_body or html_body is required.
html_bodystringoptionalHTML email body. At least one of text_body or html_body is required.
topic_idstringrequiredOnly contacts subscribed to this topic will receive the broadcast. Required.
reply_tostringoptionalReply-To address.
scheduled_atISO 8601optionalFuture timestamp to schedule the broadcast. Omit to send immediately.
Response
idstringBroadcast ID.
statusstringqueued or scheduled.
stats.totalnumberNumber of eligible contacts the broadcast will be sent to.
stats.sentnumberEmails successfully sent so far.
stats.failednumberEmails that failed after retries.
stats.skippednumberContacts skipped due to unsubscribe or topic filter.
scheduled_atISO 8601 | nullScheduled send time, if applicable.
created_atISO 8601Creation timestamp.
Request (curl)
curl -X POST https://sendexapi.com/api/v1/broadcasts \
-H "Authorization: Bearer sk_live_XXXX" \
-H "Content-Type: application/json" \
-d '{
"audience_id": "66f1a2b3c4d5e6f7a8b9c0e1",
"topic_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"from": "[email protected]",
"subject": "Our May changelog",
"html_body": "<h1>What'''s new</h1><p>Here is what we shipped this month...</p>"
}'Response
HTTP/1.1 201 Created
{
"id": "66f1a2b3c4d5e6f7a8b9d0a1",
"status": "queued",
"stats": {
"total": 2000,
"sent": 0,
"failed": 0,
"skipped": 0
},
"scheduled_at": null,
"created_at": "2025-05-01T10:00:00.000Z"
}List broadcasts
Returns a paginated list of broadcasts. Optionally filter by domain or status.
Query Parameters
domainstringoptionalFilter by sending domain.
statusstringoptionalFilter by status: draft, queued, scheduled, sending, paused, completed, failed, cancelled.
limitnumberoptionalResults per page (1–100). Default 20.
offsetnumberoptionalPagination offset. Default 0.
Response
dataBroadcast[]Array of broadcast objects.
totalnumberTotal matching broadcasts.
limitnumberLimit applied.
offsetnumberOffset applied.
Request (curl)
curl "https://sendexapi.com/api/v1/broadcasts?domain=example.com&status=completed" \ -H "Authorization: Bearer sk_live_XXXX"
Response
{
"data": [
{
"id": "66f1a2b3c4d5e6f7a8b9d0a1",
"audience_id": "66f1a2b3c4d5e6f7a8b9c0e1",
"domain": "example.com",
"from": "[email protected]",
"subject": "Our May changelog",
"topic_id": null,
"status": "completed",
"stats": { "total": 2000, "sent": 1987, "failed": 3, "skipped": 10 },
"scheduled_at": null,
"started_at": "2025-05-01T10:00:05.000Z",
"completed_at": "2025-05-01T10:02:28.000Z",
"created_at": "2025-05-01T10:00:00.000Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}Retrieve a broadcast
Returns a single broadcast with live stats. Poll this endpoint to track send progress.
Response
idstringBroadcast ID.
audience_idstringTarget audience ID.
domainstringSending domain.
fromstringSender address.
subjectstringSubject line.
topic_idstring | nullTopic filter applied, if any.
reply_tostring | nullReply-To address.
statusstringCurrent status.
statsobject{ total, sent, failed, skipped }
scheduled_atISO 8601 | nullScheduled time.
started_atISO 8601 | nullWhen sending began.
completed_atISO 8601 | nullWhen sending finished.
failed_atISO 8601 | nullWhen the broadcast failed, if applicable.
created_atISO 8601Creation timestamp.
Request (curl)
curl "https://sendexapi.com/api/v1/broadcasts/66f1a2b3c4d5e6f7a8b9d0a1" \ -H "Authorization: Bearer sk_live_XXXX"
Response
{
"id": "66f1a2b3c4d5e6f7a8b9d0a1",
"audience_id": "66f1a2b3c4d5e6f7a8b9c0e1",
"domain": "example.com",
"from": "[email protected]",
"subject": "Our May changelog",
"topic_id": null,
"reply_to": null,
"status": "sending",
"stats": { "total": 2000, "sent": 840, "failed": 0, "skipped": 2 },
"scheduled_at": null,
"started_at": "2025-05-01T10:00:05.000Z",
"completed_at": null,
"failed_at": null,
"created_at": "2025-05-01T10:00:00.000Z",
"updated_at": "2025-05-01T10:01:02.000Z"
}Pause or resume a broadcast
Pause a sending or queued broadcast, or resume a paused one. The worker stops between chunk boundaries — already-in-flight emails will still be delivered.
Request Body
actionstringrequired"pause" or "resume".
Response
idstringBroadcast ID.
statusstringNew status: paused or queued.
Request (curl)
curl -X PATCH "https://sendexapi.com/api/v1/broadcasts/66f1a2b3c4d5e6f7a8b9d0a1" \
-H "Authorization: Bearer sk_live_XXXX" \
-H "Content-Type: application/json" \
-d '{ "action": "pause" }'Response
{
"id": "66f1a2b3c4d5e6f7a8b9d0a1",
"status": "paused"
}Cancel a broadcast
Cancels a queued, scheduled, or paused broadcast. Broadcasts that are already sending cannot be cancelled — pause first.
Response
idstringBroadcast ID.
statusstringAlways cancelled.
Request (curl)
curl -X DELETE "https://sendexapi.com/api/v1/broadcasts/66f1a2b3c4d5e6f7a8b9d0a1" \ -H "Authorization: Bearer sk_live_XXXX"
Response
{
"id": "66f1a2b3c4d5e6f7a8b9d0a1",
"status": "cancelled"
}List send logs
Returns a paginated per-contact log for a broadcast. Useful for debugging failures or auditing delivery. Logs are retained for 30 days after processing and then automatically deleted.
Query Parameters
statusstringoptionalFilter by: pending, sent, failed, skipped.
limitnumberoptionalResults per page (1–200). Default 50.
offsetnumberoptionalPagination offset. Default 0.
Response
dataLog[]Array of log entries.
data[].idstringLog entry ID.
data[].contact_idstringContact ID.
data[].emailstringRecipient email address.
data[].statusstringpending, sent, failed, or skipped.
data[].skip_reasonstring | nullunsubscribed or topic_unsubscribed if skipped.
data[].ses_message_idstring | nullSES message ID on success.
data[].error_messagestring | nullError detail on failure.
data[].processed_atISO 8601 | nullWhen this contact was processed.
totalnumberTotal log entries matching the filter.
Request (curl)
curl "https://sendexapi.com/api/v1/broadcasts/66f1a2b3c4d5e6f7a8b9d0a1/logs?status=failed" \ -H "Authorization: Bearer sk_live_XXXX"
Response
{
"data": [
{
"id": "66f1a2b3c4d5e6f7a8b9d1b2",
"contact_id": "66f1a2b3c4d5e6f7a8b9c0f1",
"email": "[email protected]",
"status": "failed",
"skip_reason": null,
"ses_message_id": null,
"error_message": "Throttling: Maximum sending rate exceeded.",
"attempt_count": 3,
"processed_at": "2025-05-01T10:01:15.000Z"
}
],
"total": 3,
"limit": 50,
"offset": 0
}