mcpField
B
broadcast-campaign

broadcast-campaign

github.com/zavudevMarketing & growth1240 installsUpdated today
Repository

About broadcast-campaign

broadcast-campaign is a skill for building Zavu campaigns that send messages to multiple recipients, including bulk email. It covers broadcast creation, contact management, review and scheduling, as well as progress monitoring and rejection handling.

Key features

  1. 01Create broadcasts across messaging channels
  2. 02Add contacts in batches with variables
  3. 03Submit, schedule, pause, or cancel campaigns
  4. 04Track delivery and recipient statuses
  5. 05Handle content-review rejection and retries

Use cases

  1. 01Send a bulk email newsletter
  2. 02Schedule a multi-recipient SMS campaign
  3. 03Monitor delivery and failed recipients
  4. 04Revise a broadcast rejected in content review

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/zavudev/zavu-skills --skill broadcast-campaign

After installing, type/broadcast-campaignin Claude Code to confirm it loaded.

Details

From the project README

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)
         draft | pending_review | pending_admin_review | approved
           | rejected | escalated | scheduled | sending -> 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
ChannelDescription
smartPer-contact intelligent routing
smsSMS to all contacts
sms_onewayOne-way SMS (no replies) — needs no number and no credential
whatsappWhatsApp (requires template for non-window contacts)
telegramTelegram. Recipients are numeric chat IDs (838360395 or telegram:838360395) of people who already wrote to your bot; a bot cannot start a chat, and phone numbers are rejected
emailEmail (needs emailSubject) — recommended path for bulk email
Message Types
TypeDescription
textPlain text (default)
imageImage with optional caption
videoVideo message
audioAudio message
documentDocument file
templateWhatsApp 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.

Email broadcasts need a paid plan. On Free, sending or scheduling one returns 402 with code: "paid_plan_required" (drafts can still be created). A smart broadcast still sends, but its email recipients fail with errorCode: "PAID_PLAN_REQUIRED". Transactional email through POST /v1/messages works on every plan.

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 a broadcast needs no account verification, on any channel. Identity and business verification raise daily ceilings; neither is a permission to broadcast. What stands in front of a broadcast is the content review, and it cannot be bypassed.

The broadcast's own text is read once — not once per recipient — before the automated review runs. A broadcast that is refused lands on rejected; one that is flagged waits for a person even on email, which otherwise sends as soon as the review passes it.

A whatsapp broadcast skips review. It can only be built on a template, and Meta vets the business and the content when it approves that template. 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.

Each recipient is then sent as an ordinary message, so the daily ceilings and the 10DLC requirement on +1 SMS destinations apply per recipient.

The team owner gets an email when the review answers: approved (sending, scheduled, or ready to send), or not approved, with the reason and what to do next. A broadcast whose sender was deleted after it was created is refused with 400 sender_not_found before any balance is reserved; create a new broadcast with another sender.

// 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:

BroadcastWhat this call does
WhatsApp on a Meta-approved templateSkips review (Meta already vetted it) and starts sending
EmailAutomated review; sends as soon as it passes
SMS, Telegram, smart, everything elseAutomated 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.

When the balance cannot cover the estimated cost, send returns 402 with the shared error shape at the top level: { code: "insufficient_balance", message, details: { totalBalance, reservedBalance, availableBalance } }. The same object is also repeated under error for older integrations.

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: not handed to the provider yet.
  • sent: the provider accepted the message; delivery is not confirmed yet. A recipient moves on to delivered or failed when the channel reports it. Channels that never report delivery leave the recipient at sent.
  • delivered: the channel confirmed delivery to the device (a WhatsApp read receipt counts too).
  • failed: not delivered. A recipient can move from sent or delivered to failed when the provider reports a failure late.
  • skipped: not sent, because the recipient opted out of the channel or the broadcast was cancelled before reaching it.

The progress response and the broadcast carry the sent count as sent and sentCount, and sent is a valid status filter on GET /v1/broadcasts/{broadcastId}/contacts. The SDK does not type them yet, so read them from the REST response:

curl -s https://api.zavu.dev/v1/broadcasts/$BROADCAST_ID/progress \
  -H "Authorization: Bearer $ZAVU_API_KEY"
# { "total": 500, "sent": 120, "delivered": 360, "failed": 20, ... }

Completion counts sent as done, so a completed broadcast can still have recipients waiting on a delivery receipt.

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). Allowed from
// draft, pending_review, pending_admin_review, approved, rejected, escalated,
// scheduled and sending; not from paused or a finished status. Any reserved
// balance is released.
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 add request (batch for larger lists)
  • Sending needs no account verification on any channel. What gates a broadcast is the content review below; drafting requires nothing
  • Email broadcasts need a paid plan: on Free, send returns 402 paid_plan_required
  • Each recipient counts against the channel's daily ceiling (see the send-message skill); once it is reached the remaining recipients are marked failed with errorCode: "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; email is the exception and sends straight through, unless the review asks for a person
  • Balance is reserved (estimated cost) when sending
  • Max 3 review retry attempts, then escalate
  • Can only update/delete broadcasts in draft status
  • Cancelling doesn't stop already-queued messages

Comments

Markdown supported