Broadcast Campaign
When to Use
Use this skill when building code to send messages to multiple recipients in a campaign — including bulk email. Covers the full broadcast lifecycle from creation to monitoring.
Broadcast Lifecycle
draft -> pending_review -> pending_admin_review -> approved -> sending -> completed
-> rejected -> (edit) -> pending_review (retry, max 3)
-> rejected -> escalated (manual review by Zavu team)
-> rejected_final
approved -> scheduled -> sending -> completed
sending -> paused (can resume)
(any active) -> cancelled
(any) -> failed (permanent failure)
Step-by-Step Workflow
1. Create Broadcast
const result = await zavu.broadcasts.create({
name: "Black Friday Sale",
channel: "sms", // smart | sms | sms_oneway | whatsapp | telegram | email
text: "Hi {{name}}, check out our Black Friday deals! Code: FRIDAY20",
});
const broadcastId = result.broadcast.id; // brd_xxx
Python:
result = zavu.broadcasts.create(
name="Black Friday Sale",
channel="sms",
text="Hi {{name}}, check out our Black Friday deals! Code: FRIDAY20",
)
broadcast_id = result.broadcast.id
Go:
result, err := client.Broadcasts.Create(context.TODO(), zavudev.BroadcastCreateParams{
Name: zavudev.String("Black Friday Sale"),
Channel: zavudev.String("sms"),
Text: zavudev.String("Hi {{name}}, check out our Black Friday deals! Code: FRIDAY20"),
})
broadcastID := result.Broadcast.ID
Ruby:
result = client.broadcasts.create(
name: "Black Friday Sale",
channel: "sms",
text: "Hi {{name}}, check out our Black Friday deals! Code: FRIDAY20",
)
broadcast_id = result.broadcast.id
PHP:
$result = $client->broadcasts->create([
'name' => 'Black Friday Sale',
'channel' => 'sms',
'text' => 'Hi {{name}}, check out our Black Friday deals! Code: FRIDAY20',
]);
$broadcastId = $result->broadcast->id;
Channel Options
| Channel | Description |
|---|---|
smart |
Per-contact intelligent routing |
sms |
SMS to all contacts |
sms_oneway |
One-way SMS (no replies) |
whatsapp |
WhatsApp (requires template for non-window contacts) |
telegram |
Telegram |
email |
Email (needs emailSubject) — recommended path for bulk email |
Message Types
| Type | Description |
|---|---|
text |
Plain text (default) |
image |
Image with optional caption |
video |
Video message |
audio |
Audio message |
document |
Document file |
template |
WhatsApp pre-approved template |
Email Broadcast (with HTML body)
For bulk email campaigns, use channel: "email" with an HTML body. This is the recommended way to send mass email through Zavu.
const result = await zavu.broadcasts.create({
name: "Newsletter",
channel: "email",
emailSubject: "Special offer for {{name}}",
text: "Hi {{name}}, check out our latest sale!", // plain text fallback
emailHtmlBody: "<h1>Hi {{name}}!</h1><p>Check out our latest sale.</p>",
metadata: { campaign_id: "camp_123", region: "US" },
});
2. Add Contacts (batch, max 1000/request)
const result = await zavu.broadcasts.contacts.add({
broadcastId: broadcastId,
contacts: [
{ recipient: "+14155551234", templateVariables: { name: "John" } },
{ recipient: "+14155555678", templateVariables: { name: "Jane" } },
],
});
console.log(result.added, result.duplicates, result.invalid);
3. Send (triggers content review)
Sending requires the account to be past the unverified floor. Any one of these clears it, and they are not equally slow:
| Route | What it takes |
|---|---|
| Payment method | Add a card, or settle the deposit — about half a minute |
| Paid plan | Any paid subscription |
| Identity verification (KYC) | A document and a selfie, a few minutes |
Without one, the send is refused with 403 kyc_required and the message
"Verify your identity, add a payment method, or upgrade before sending broadcasts. You can keep editing this draft in the meantime."
(details.dashboardUrl is /kyc). Business verification (KYB) is not
required to broadcast: it gates 10DLC registration, nothing here.
A whatsapp broadcast is exempt. It can only be built on a template, and
Meta vets the business and the content when it approves that template, so the
code is never returned for one. What is enforced instead is that the template is
approved — an unapproved one is refused with 400 template_not_approved, and
since WhatsApp passes no other review, that is the only gate on it. smart is
not exempt: it can route a contact to SMS or email.
Creating and editing drafts needs no check at all — everything above works unverified, and only this call is blocked.
// Send immediately
await zavu.broadcasts.send({ broadcastId });
// Or schedule
await zavu.broadcasts.send({
broadcastId,
scheduledAt: "2024-01-15T10:00:00Z",
});
A draft never goes straight out — what happens next depends on the channel:
| Broadcast | What this call does |
|---|---|
| WhatsApp on a Meta-approved template | Skips review (Meta already vetted it) and starts sending |
| Automated review; sends as soon as it passes | |
| SMS, Telegram, smart, everything else | Automated review, then pending_admin_review — a person approves before it sends |
So a 202 means accepted, not sending. Poll the status rather than
assuming; only WhatsApp-template broadcasts move to sending immediately.
Calling send on a broadcast that is already approved or scheduled sends or
reschedules it directly, since it has already been through review.
4. Monitor Progress
const progress = await zavu.broadcasts.progress({ broadcastId });
console.log(`${progress.percentComplete}% complete`);
console.log(`Delivered: ${progress.delivered}, Failed: ${progress.failed}, Skipped: ${progress.skipped}`);
console.log(`Estimated completion: ${progress.estimatedCompletionAt}`);
Per-contact statuses: pending, queued, sending, delivered, failed, skipped (excluded — opted out, duplicate, or invalid).
5. Handle Rejection (if content review fails)
// Check remaining review attempts
const broadcast = await zavu.broadcasts.get({ broadcastId });
console.log(`Review attempts: ${broadcast.reviewAttempts}/3`);
// Edit content
await zavu.broadcasts.update({
broadcastId,
text: "Updated message content with {{name}}",
});
// Retry review (max 3 attempts)
await zavu.broadcasts.retryReview({ broadcastId });
// Or escalate to manual review
await zavu.broadcasts.escalate({ broadcastId });
Other Operations
// Reschedule
await zavu.broadcasts.reschedule({
broadcastId, scheduledAt: "2024-01-16T14:00:00Z",
});
// Cancel (pending contacts skipped, queued may still deliver)
await zavu.broadcasts.cancel({ broadcastId });
// List contacts in broadcast
const contacts = await zavu.broadcasts.contacts.list({
broadcastId, status: "delivered", limit: 100,
});
// Delete (draft only)
await zavu.broadcasts.delete({ broadcastId });
Template Variables
Use {{variable_name}} in broadcast text. Override per-contact via templateVariables:
await zavu.broadcasts.contacts.add({
broadcastId,
contacts: [
{ recipient: "+14155551234", templateVariables: { name: "John", order_id: "ORD-001" } },
],
});
Constraints
- Max 1000 contacts per
addrequest (batch for larger lists) - Sending requires the account past the unverified floor — a payment method, a paid plan or KYC, any one of them (
403 kyc_requiredotherwise) — on every channel exceptwhatsapp, which is exempt because Meta's template approval stands in for it;smartis not exempt. KYB is not required. Drafting requires nothing - Each recipient counts against the channel's daily ceiling (see the
send-messageskill); once it is reached the remaining recipients are markedfailedwitherrorCode: "DAILY_LIMIT_EXCEEDED"and are not retried the next day - Content goes through review before sending, except WhatsApp on a Meta-approved template
- Most channels also wait on a human (
pending_admin_review) after the automated pass - Balance is reserved (estimated cost) when sending
- Max 3 review retry attempts, then escalate
- Can only update/delete broadcasts in
draftstatus - Cancelling doesn't stop already-queued messages