Broadcasts
The broadcasts (plural) and broadcast (singular) commands mirror the
/broadcasts and /broadcasts/<id> pages. From the terminal you can
list every campaign, drill into one, see per-message delivery, cancel an
in-flight broadcast, send a scheduled one immediately, restart a partially
failed run, and rebroadcast a cohort.
Every write command runs the same subscription + wallet preflight that the page does before submitting; the backend still enforces both at send time. A broadcast that fails preflight prints why and exits non-zero without burning a wallet hold.
Quick start
splashify broadcasts # list every campaign
splashify broadcast stats # overall stats card
splashify broadcast <broadcast_id> # campaign detail
splashify broadcast create \
--name "May Sale" \
--template may_offer \
--audience-type segment \
--audience-id <segment_id> \
--schedule 2026-06-01T10:00:00ZCommand reference
splashify broadcasts — list campaigns
splashify broadcasts # all| Backed by | GET /api/v1/app/broadcasts |
|---|
Response shape (per row):
{
"broadcast_id": "uuid",
"name": "May Sale",
"template_name": "may_offer",
"audience_type": "segment", // segment | tag | all
"audience_id": "...",
"status": "scheduled", // draft | scheduled | running | completed | cancelled | failed
"schedule_at": "2026-06-01T10:00:00Z",
"started_at": null,
"completed_at": null,
"created_at": "...",
"total_recipients": 1245,
"sent_count": 0,
"delivered_count":0,
"read_count": 0,
"failed_count": 0
}splashify broadcast stats — global stats
splashify broadcast stats| Backed by | GET /api/v1/app/broadcasts/stats |
|---|
Returns counts across every broadcast (total / completed / running / failed) plus aggregate delivery metrics.
splashify broadcast <broadcast_id> — campaign detail
splashify broadcast 7a32bce6-…| Backed by | GET /api/v1/app/broadcasts/:broadcast_id |
|---|
Returns the full broadcast row plus the cohort counts and progress breakdown shown on the detail page.
splashify broadcast create — schedule a new campaign
splashify broadcast create \
--name "May Sale" \
--template may_offer \
--audience-type segment \
--audience-id <segment_id> \
--schedule 2026-06-01T10:00:00Z| Backed by | POST /api/v1/app/broadcasts |
|---|
| Flag | Required | Notes |
|---|---|---|
--name | yes | Display name |
--template | yes | Approved WhatsApp template name (must be APPROVED — see Templates) |
--audience-type | yes | segment | tag | all |
--audience-id | when type=segment|tag | segment_id (UUID) or tag name |
--schedule | no | ISO 8601 UTC; omit to send immediately |
Preflight checks before the round-trip:
- Subscription — the account must have an active plan OR unexpired trial.
A
subscription_expiredrefusal points to/settings/subscriptions. - Balance — the wallet must cover the estimated cost (recipients × per-message rate for the template’s category). If short, the CLI surfaces the shortfall and the recharge link.
If either fails the CLI exits with the friendly error and does not create the broadcast. Once submitted, the backend’s broadcast worker enforces both again at send time (defence in depth).
Lifecycle actions
Per-id actions live after the broadcast id — splashify broadcast <id> <action>.
splashify broadcast <broadcast_id> cancel # stop a scheduled/running broadcast
splashify broadcast <broadcast_id> send-now # send a `scheduled` broadcast immediately
splashify broadcast <broadcast_id> restart # retry the failed cohort on a `failed`/partial broadcast
splashify broadcast <broadcast_id> rebroadcast \
--cohort failed|sent|delivered|read|all # re-target a specific cohort on a fresh row| Backed by | Verb | Endpoint |
|---|---|---|
cancel | POST | /app/broadcasts/:id/cancel |
send-now | POST | /app/broadcasts/:id/send-now |
restart | POST | /app/broadcasts/:id/restart |
rebroadcast | POST | /app/broadcasts/:id/rebroadcast |
All four run the same preflight as create. cancel is irreversible —
once a broadcast moves to cancelled, you re-broadcast or create a new
one.
Per-message inspection
splashify broadcast <broadcast_id> messages [--page N] [--limit N] [--status sent|delivered|read|failed]
splashify broadcast <broadcast_id> progress # live SSE stream of progress events
splashify broadcast <broadcast_id> progress --once # print the first event and exit
splashify broadcast <broadcast_id> progress --max 10 # stop after N events
splashify broadcast <broadcast_id> cohorts # cohort counts (failed / sent / delivered / read)
splashify broadcast <broadcast_id> --recompute # refresh the local rollup (Refresh button)| Backed by | GET /app/broadcasts/:id/messages?page=&limit=&status= |
|---|---|
GET /app/broadcasts/:id/progress (Server-Sent Events) | |
GET /app/broadcasts/:id/cohort-counts | |
GET /app/broadcasts/:id?recompute=true |
--recompute is the page’s “Refresh” button — it re-aggregates the
per-message rows into the broadcast row’s counters. Use it if the totals
look stale. progress streams Server-Sent Events the same way the
/broadcasts/<id>/progress page does; the loop ends when the broadcast
reaches a terminal state (completed, cancelled, failed) or when
--max / --once is satisfied.
CSV export
splashify broadcast <broadcast_id> export > campaign.csv
# Filter by message status before exporting
splashify broadcast <broadcast_id> export --status FAILED > failed.csv
splashify broadcast <broadcast_id> export --csv # force the CSV content-type| Backed by | GET /app/broadcasts/:id/export |
|---|
Streams the same CSV the page’s Export CSV button produces — one row per recipient with phone, status, sent_at, delivered_at, failed_reason, etc. The CLI writes the raw response to stdout so you can redirect or pipe.
Live progress (SSE)
splashify broadcast <broadcast_id> progress # follow until terminal status
splashify broadcast <broadcast_id> progress --once # snapshot (first event, then exit)
splashify broadcast <broadcast_id> progress --max 10 # stop after N events| Backed by | GET /app/broadcasts/:id/progress (Server-Sent Events) |
|---|
Mirrors the /broadcasts/<id>/progress page. The CLI connects
to the SSE stream, prints one JSON object per event (running counts +
status), and exits cleanly when the broadcast reaches a terminal state
(COMPLETED, CANCELLED, or FAILED). Aliases: watch, stream.
Each event has the shape:
{
"broadcast_id": "uuid",
"status": "SENDING",
"pct": 42.3,
"counts": {
"pending": 100,
"sent": 850,
"delivered": 820,
"read": 612,
"failed": 13
}
}Common workflows
List broadcasts that need attention
splashify broadcasts | jq '.broadcasts[] |
select(.status == "failed" or .failed_count > 0) |
{broadcast_id, name, status, failed_count, total_recipients}'Watch a broadcast finish
# Recommended: follow the SSE stream and exit on terminal status
splashify broadcast 7a32bce6-… progressNeed a polling loop instead (e.g. logging snapshots every 30s)?
BID=7a32bce6-…
while : ; do
STATUS=$(splashify broadcast "$BID" | jq -r '.status // .broadcast.status')
PROG=$(splashify broadcast "$BID" progress --once | jq -c '.counts // .')
echo "$(date -u +%H:%M:%S) $STATUS $PROG"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "cancelled" ] || [ "$STATUS" = "failed" ] && break
sleep 30
doneSend to every “VIP” contact
SEGMENT_ID=$(splashify segments --search "VIP" | jq -r '.segments[0].id')
splashify broadcast create \
--name "VIP exclusive — May" \
--template vip_may_offer \
--audience-type segment \
--audience-id "$SEGMENT_ID"Cancel every scheduled broadcast (e.g. emergency stop)
splashify broadcasts | \
jq -r '.broadcasts[] | select(.status == "scheduled") | .broadcast_id' | \
xargs -I{} splashify broadcast {} cancelRebroadcast to recipients who failed
splashify broadcast <broadcast_id> rebroadcast --cohort failed
# Backend re-targets only the recipients whose message status is `failed`,
# at the same template, on a fresh broadcast row.Troubleshooting
subscription_expired on create — the account has no active paid
plan and the trial is over. Open /settings/subscriptions or run
splashify subscription for status + upgrade URL.
insufficient_balance on create — wallet won’t cover the estimated
cost. The error includes the shortfall amount and a recharge link.
splashify wallet shows the current balance; recharge from the app.
Template not found / not approved — --template takes the template
name, not the ID. The template must be APPROVED on Meta — run
splashify templates sync to refresh status, then splashify template <id> to confirm.
audience_id ignored — required only when --audience-type is
segment or tag. For all, omit it.
Counts look wrong on the detail view — run splashify broadcast recompute-stats <id>. The page’s per-row counts come from a rollup that
can lag during heavy bursts.
Cancelled but still receiving deliveries — cancellation stops scheduling of new messages. Messages already in Meta’s outbound queue get delivered; webhook updates land normally. Wait ~1 minute after cancel to see the final counts settle.
See also
splashify templates— the source of--templatenames.splashify segments— get the--audience-idfor a saved audience.splashify wallet— wallet balance and history.splashify expenses— per-message billing log including broadcast charges.