Sendex

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

  1. Each recipient has a unique, signed unsubscribe token stored on their contact record.
  2. At send time, Sendex replaces every {{unsubscribe_link}} occurrence with that recipient's personal unsubscribe URL.
  3. When the recipient visits the link, their contact is marked as unsubscribed and a contact.unsubscribed webhook fires.
  4. 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.

POST/api/v1/broadcastsfull access

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_idstringrequired

ID of the audience to send to.

fromstringrequired

Sender address. Must belong to a verified domain on your account.

from_namestringoptional

Sender display name. Recipients see "Name <email>" instead of just the address.

subjectstringrequired

Email subject line.

text_bodystringoptional

Plain-text email body. At least one of text_body or html_body is required.

html_bodystringoptional

HTML email body. At least one of text_body or html_body is required.

topic_idstringrequired

Only contacts subscribed to this topic will receive the broadcast. Required.

reply_tostringoptional

Reply-To address.

scheduled_atISO 8601optional

Future timestamp to schedule the broadcast. Omit to send immediately.

Response

idstring

Broadcast ID.

statusstring

queued or scheduled.

stats.totalnumber

Number of eligible contacts the broadcast will be sent to.

stats.sentnumber

Emails successfully sent so far.

stats.failednumber

Emails that failed after retries.

stats.skippednumber

Contacts skipped due to unsubscribe or topic filter.

scheduled_atISO 8601 | null

Scheduled send time, if applicable.

created_atISO 8601

Creation 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"
}
GET/api/v1/broadcastsfull access

List broadcasts

Returns a paginated list of broadcasts. Optionally filter by domain or status.

Query Parameters

domainstringoptional

Filter by sending domain.

statusstringoptional

Filter by status: draft, queued, scheduled, sending, paused, completed, failed, cancelled.

limitnumberoptional

Results per page (1–100). Default 20.

offsetnumberoptional

Pagination offset. Default 0.

Response

dataBroadcast[]

Array of broadcast objects.

totalnumber

Total matching broadcasts.

limitnumber

Limit applied.

offsetnumber

Offset 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
}
GET/api/v1/broadcasts/:idfull access

Retrieve a broadcast

Returns a single broadcast with live stats. Poll this endpoint to track send progress.

Response

idstring

Broadcast ID.

audience_idstring

Target audience ID.

domainstring

Sending domain.

fromstring

Sender address.

subjectstring

Subject line.

topic_idstring | null

Topic filter applied, if any.

reply_tostring | null

Reply-To address.

statusstring

Current status.

statsobject

{ total, sent, failed, skipped }

scheduled_atISO 8601 | null

Scheduled time.

started_atISO 8601 | null

When sending began.

completed_atISO 8601 | null

When sending finished.

failed_atISO 8601 | null

When the broadcast failed, if applicable.

created_atISO 8601

Creation 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"
}
PATCH/api/v1/broadcasts/:idfull access

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

idstring

Broadcast ID.

statusstring

New 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"
}
DELETE/api/v1/broadcasts/:idfull access

Cancel a broadcast

Cancels a queued, scheduled, or paused broadcast. Broadcasts that are already sending cannot be cancelled — pause first.

Response

idstring

Broadcast ID.

statusstring

Always cancelled.

Request (curl)

curl -X DELETE "https://sendexapi.com/api/v1/broadcasts/66f1a2b3c4d5e6f7a8b9d0a1" \
  -H "Authorization: Bearer sk_live_XXXX"

Response

{
  "id": "66f1a2b3c4d5e6f7a8b9d0a1",
  "status": "cancelled"
}
GET/api/v1/broadcasts/:id/logsfull access

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

statusstringoptional

Filter by: pending, sent, failed, skipped.

limitnumberoptional

Results per page (1–200). Default 50.

offsetnumberoptional

Pagination offset. Default 0.

Response

dataLog[]

Array of log entries.

data[].idstring

Log entry ID.

data[].contact_idstring

Contact ID.

data[].emailstring

Recipient email address.

data[].statusstring

pending, sent, failed, or skipped.

data[].skip_reasonstring | null

unsubscribed or topic_unsubscribed if skipped.

data[].ses_message_idstring | null

SES message ID on success.

data[].error_messagestring | null

Error detail on failure.

data[].processed_atISO 8601 | null

When this contact was processed.

totalnumber

Total 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
}