Broadcasts REST API
Endpoints for monitoring and managing email broadcasts. Broadcasts are bulk email sends to a show’s subscriber list (or a filtered subset). No premium license is required to use these endpoints.
All endpoints require manage_options and a valid X-WP-Nonce header.
GET /benecaster/v1/broadcasts
Returns a paginated list of broadcast records for a show. Every broadcast sent is listed, including one that mailed nobody because every recipient had opted out, and one whose recipient rows the retention purge has since deleted (it keeps its sent and failed counts).
Auth: manage_options + X-WP-Nonce
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
show_id |
int | 0 | Show to list broadcasts for. 0 lists every show’s broadcasts |
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (max 100) |
Response:
{
"items": [
{
"id": 12,
"show_id": 45,
"subject": "New episode: Season finale is here",
"preheader": "Forty minutes on the last day of shooting",
"audiences": ["subscribers", "followers"],
"author_id": 1,
"created_at": "2026-05-17 09:00:00",
"completed_at": "2026-05-17 09:06:00",
"recipients": 261,
"suppressed": 12,
"skipped": 2,
"total": 247,
"sent": 245,
"failed": 2,
"pending": 0,
"body_purged": false,
"status": "complete_with_failures"
}
],
"total": 8
}
Newest first. The response is items and total only.
| Field | Meaning |
|---|---|
audiences |
Who it was addressed to, as the audience keys it was sent with |
author_id |
WordPress user who sent it |
recipients |
Everyone the audiences resolved to, set when the broadcast is sent |
suppressed |
Not mailed because they had opted out |
skipped |
Not mailed because they had no email address, or the email could not be queued |
total |
Recipients handed to the email queue so far |
sent / failed / pending |
Queue progress. pending counts recipients still queued or being sent |
completed_at |
When the last recipient was handed to the queue; null until then |
body_purged |
true once the retention purge has deleted the message body |
Recipients are handed to the queue in the background. benecaster_send_broadcast() returns at once, and scheduled tasks then queue the recipients in batches of 100. Until the last batch, the broadcast lists as sending, with recipients already the full audience while total, suppressed and skipped rise batch by batch, and completed_at is null. On a large audience this can take a few minutes, longer on a site whose scheduled tasks run only when someone visits. created_at is when the broadcast was queued, as a UTC Y-m-d H:i:s string with no zone marker; treat it as UTC before parsing, the same as sent_at below.
Status values:
| Status | Meaning |
|---|---|
queued |
Every recipient handed to the queue; nothing sent or failed yet |
sending |
Still handing recipients to the queue, or some recipients done and others waiting |
complete |
Every recipient sent, none failed. Also a broadcast that mailed nobody, with suppressed saying why |
complete_with_failures |
Every recipient finished; one or more failed |
There is no status filter: the status is worked out from each broadcast’s counts.
The Broadcasts admin screen polls this endpoint every 30 seconds while any broadcast has status queued or sending.
GET /benecaster/v1/broadcasts/{id}/recipients
Returns per-recipient delivery status for a broadcast, plus a summary of who was not mailed. Answers 404 only for an id with no broadcast record; a broadcast with no recipient rows (everyone opted out, or the first batch has not run yet) gets an empty list with 200.
Auth: manage_options + X-WP-Nonce
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
per_page |
int | 50 | Items per page (max 200) |
status |
string | — | Filter by status: pending, sending, sent, failed, or in_progress (pending and sending together). Omit to list every recipient |
Response:
{
"items": [
{
"queue_id": 4821,
"user_id": 112,
"display_name": "Sarah Chen",
"email": "sarah@example.com",
"status": "sent",
"attempts": 1,
"last_error": null,
"sent_at": "2026-05-17 09:03:00",
"created_at": "2026-05-17 09:00:00"
},
{
"queue_id": 4822,
"user_id": 113,
"display_name": "Bob Martinez",
"email": "bob@example.com",
"status": "failed",
"attempts": 3,
"last_error": "SMTP connection timeout",
"sent_at": null,
"created_at": "2026-05-17 09:00:00"
}
],
"total": 247,
"summary": {
"recipients": 261,
"suppressed": 12,
"skipped": 2,
"status": "sent"
}
}
Work out the page count from total and the per_page you asked for.
summary comes from the broadcast record, not the recipient rows, and ignores the status filter. It is the only place one broadcast’s not-mailed figures are returned, because opted-out and skipped listeners have no rows. Its status is the record’s own: sending while recipients are still being handed to the queue, sent once all have been, or '' if the record could not be read. It is what tells apart the two cases with no rows at all: sent means the broadcast finished having queued nobody, sending means its first batch has not run yet. Rows come oldest first.
sent_at and created_at are UTC, with no zone marker. Both are Y-m-d H:i:s strings; sent_at is null until that recipient has been sent, and created_at is when they were queued. Treat them as UTC before parsing: for example, replace the space with T and append Z. Parsed as they stand, most JavaScript runtimes read them as the viewer’s local time and show the wrong hour.
display_name and email are read from the recipient’s WordPress account, and are empty strings for a recipient whose account has since been deleted.
Recipient status values:
| Status | Meaning |
|---|---|
pending |
Queued, including a failed recipient put back by a retry — unless they have opted out since, in which case the retry leaves them failed (see below) |
sending |
Being sent now |
sent |
Handed to your mail server |
failed |
All delivery attempts exhausted; last_error says why |
POST /benecaster/v1/broadcasts/{id}/retry-all
Puts every failed recipient row in a broadcast back to pending, so it is sent again. Recipients that already succeeded are unaffected.
A recipient who has opted out since their copy failed is not retried. Their row stays failed, last_error reads Not retried: the recipient has opted out (…), and the email log records the email as suppressed. A retry never reaches someone who has asked to stop.
Auth: manage_options + X-WP-Nonce
Response:
{
"count": 2
}
count is the number of rows put back to pending. It is 0 when there is nothing to retry, including for a broadcast ID with no rows.
POST /benecaster/v1/broadcasts/{id}/recipients/{qid}/retry
Puts one failed recipient row back to pending, so it is sent again. {qid} is the recipient row ID from GET /broadcasts/{id}/recipients.
Auth: manage_options + X-WP-Nonce
Response:
{
"success": true
}
Errors:
| Code | HTTP | Condition |
|---|---|---|
not_retryable |
400 | The row was not found, is not failed, or its recipient has opted out since — in that last case it stays failed with Not retried: the recipient has opted out (…) in last_error |
Recipe
Recipe: Query Broadcast Delivery Status from an External Tool — use GET /broadcasts and GET /broadcasts/{id}/recipients from an external script or CI pipeline to monitor delivery after a scheduled send.
See Also
Need this built rather than just documented? See our services →