Skip to main content
Track your broadcast’s delivery progress with instant updates on sent, delivered, and failed messages.

Get Progress

const progress = await zavu.broadcasts.getProgress(broadcastId);

console.log(`Status: ${progress.status}`);
console.log(`Progress: ${progress.percentComplete}%`);
console.log(`Delivered: ${progress.delivered}/${progress.total}`);
console.log(`Failed: ${progress.failed}`);

Response

{
  "broadcastId": "brd_abc123",
  "status": "sending",
  "total": 5000,
  "pending": 2500,
  "sending": 100,
  "delivered": 2350,
  "failed": 50,
  "skipped": 0,
  "percentComplete": 48.0,
  "estimatedCost": 75.00,
  "reservedAmount": 75.00,
  "actualCost": 35.25,
  "startedAt": "2024-01-15T10:30:00.000Z",
  "estimatedCompletionAt": "2024-01-15T10:45:00.000Z"
}

Progress Fields

FieldTypeDescription
broadcastIdstringBroadcast identifier
statusstringCurrent broadcast status
totalnumberTotal contacts in broadcast
pendingnumberNot yet queued for sending
sendingnumberCurrently being sent
deliverednumberSuccessfully delivered
failednumberFailed to deliver
skippednumberSkipped (broadcast cancelled)
percentCompletenumberPercentage complete (0-100)
estimatedCostnumberEstimated total cost (USD)
reservedAmountnumberAmount reserved from balance (USD)
actualCostnumberActual cost so far (USD)
startedAtstringWhen sending started
estimatedCompletionAtstringEstimated completion time

Polling for Updates

Poll the progress endpoint at regular intervals:
async function waitForCompletion(broadcastId: string) {
  const POLL_INTERVAL = 5000; // 5 seconds

  while (true) {
    const progress = await zavu.broadcasts.getProgress(broadcastId);

    console.log(`[${new Date().toISOString()}] ${progress.percentComplete}% complete`);
    console.log(`  Delivered: ${progress.delivered}`);
    console.log(`  Failed: ${progress.failed}`);
    console.log(`  Pending: ${progress.pending}`);

    if (progress.status === "completed" || progress.status === "cancelled") {
      return progress;
    }

    await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
  }
}

const finalProgress = await waitForCompletion(broadcastId);
console.log(`Broadcast ${finalProgress.status}!`);
For large broadcasts, use longer polling intervals (10-30 seconds) to reduce API calls. The progress updates are efficient and don’t scan all contacts.

Status Transitions

draft → scheduled → sending → completed
           ↓            ↓
       cancelled    cancelled
StatuspercentCompleteDescription
draft0%Still adding contacts
scheduled0%Waiting for scheduled time
sending0-99%Messages being delivered
completed100%All messages processed
cancelledvariesStopped before completion

Viewing Individual Contacts

Get detailed status for each contact:
// Get failed contacts
const failed = await zavu.broadcasts.listContacts(broadcastId, {
  status: "failed",
  limit: 100,
});

for (const contact of failed.items) {
  console.log(`${contact.recipient}: ${contact.errorMessage}`);
}

Contact Status Response

{
  "items": [
    {
      "id": "bc_abc123",
      "recipient": "+14155551234",
      "status": "failed",
      "errorCode": "30003",
      "errorMessage": "Unreachable destination",
      "processedAt": "2024-01-15T10:35:00.000Z"
    }
  ],
  "nextCursor": null
}

Broadcast Analytics

After completion, get final statistics:
const broadcast = await zavu.broadcasts.get(broadcastId);

console.log("=== Broadcast Report ===");
console.log(`Name: ${broadcast.name}`);
console.log(`Status: ${broadcast.status}`);
console.log(`Total: ${broadcast.totalContacts}`);
console.log(`Delivered: ${broadcast.deliveredCount}`);
console.log(`Failed: ${broadcast.failedCount}`);
console.log(`Delivery Rate: ${(broadcast.deliveredCount / broadcast.totalContacts * 100).toFixed(1)}%`);
console.log(`Duration: ${calculateDuration(broadcast.startedAt, broadcast.completedAt)}`);
console.log(`Actual Cost: $${broadcast.actualCost}`);

Error Analysis

Common failure reasons:
Error CodeDescriptionRecommendation
30003Unreachable destinationInvalid or disconnected number
30004Message blockedContent filtered by carrier
30005Unknown destinationNumber doesn’t exist
30006Landline destinationCannot SMS landlines
30007Carrier violationMessage rejected by carrier
rate_limitedToo many messagesTemporary, will retry
insufficient_balanceOut of creditsTop up balance

Export Failed Contacts

async function exportFailedContacts(broadcastId: string) {
  const failed: string[] = [];
  let cursor: string | undefined;

  do {
    const result = await zavu.broadcasts.listContacts(broadcastId, {
      status: "failed",
      limit: 100,
      cursor,
    });

    for (const contact of result.items) {
      failed.push(`${contact.recipient},${contact.errorCode},${contact.errorMessage}`);
    }

    cursor = result.nextCursor ?? undefined;
  } while (cursor);

  // Write to CSV
  const csv = ["recipient,error_code,error_message", ...failed].join("\n");
  fs.writeFileSync("failed_contacts.csv", csv);

  console.log(`Exported ${failed.length} failed contacts`);
}

Real-time Updates with Webhooks

For real-time updates without polling, configure webhooks:
// In your webhook handler
app.post("/webhooks/zavu", (req, res) => {
  const event = req.body;

  if (event.type === "broadcast.completed") {
    console.log(`Broadcast ${event.data.broadcastId} completed!`);
    console.log(`Delivered: ${event.data.deliveredCount}`);
    console.log(`Failed: ${event.data.failedCount}`);
  }

  res.status(200).send("OK");
});

Broadcast Events

EventDescription
broadcast.startedBroadcast started sending
broadcast.completedAll messages processed
broadcast.cancelledBroadcast was cancelled

Best Practices

1

Use appropriate polling intervals

5 seconds for small broadcasts, 30 seconds for large ones
2

Handle partial failures

Some contacts may fail while others succeed - check both counts
3

Export and analyze failures

Review failed contacts to improve your contact list quality
4

Set up webhooks for production

Avoid continuous polling by using webhook notifications

Complete Monitoring Example

import Zavudev from '@zavudev/sdk';

const zavu = new Zavudev({
  apiKey: process.env['ZAVUDEV_API_KEY'], // This is the default and can be omitted
});

async function monitorBroadcast(broadcastId: string) {
  console.log(`Monitoring broadcast: ${broadcastId}\n`);

  let lastDelivered = 0;
  let lastFailed = 0;

  while (true) {
    const progress = await zavu.broadcasts.getProgress(broadcastId);

    // Calculate rate
    const newDelivered = progress.delivered - lastDelivered;
    const newFailed = progress.failed - lastFailed;

    console.log(`[${progress.percentComplete.toFixed(1)}%] ` +
      `Delivered: ${progress.delivered} (+${newDelivered}) | ` +
      `Failed: ${progress.failed} (+${newFailed}) | ` +
      `Pending: ${progress.pending}`);

    lastDelivered = progress.delivered;
    lastFailed = progress.failed;

    if (progress.status === "completed") {
      console.log("\n=== Broadcast Complete ===");
      console.log(`Total: ${progress.total}`);
      console.log(`Delivered: ${progress.delivered} (${(progress.delivered / progress.total * 100).toFixed(1)}%)`);
      console.log(`Failed: ${progress.failed} (${(progress.failed / progress.total * 100).toFixed(1)}%)`);
      break;
    }

    if (progress.status === "cancelled") {
      console.log("\n=== Broadcast Cancelled ===");
      console.log(`Skipped: ${progress.skipped} contacts`);
      break;
    }

    await new Promise(r => setTimeout(r, 5000));
  }
}

monitorBroadcast("brd_abc123");