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
| Field | Type | Description |
|---|
broadcastId | string | Broadcast identifier |
status | string | Current broadcast status |
total | number | Total contacts in broadcast |
pending | number | Not yet queued for sending |
sending | number | Currently being sent |
delivered | number | Successfully delivered |
failed | number | Failed to deliver |
skipped | number | Skipped (broadcast cancelled) |
percentComplete | number | Percentage complete (0-100) |
estimatedCost | number | Estimated total cost (USD) |
reservedAmount | number | Amount reserved from balance (USD) |
actualCost | number | Actual cost so far (USD) |
startedAt | string | When sending started |
estimatedCompletionAt | string | Estimated 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
| Status | percentComplete | Description |
|---|
draft | 0% | Still adding contacts |
scheduled | 0% | Waiting for scheduled time |
sending | 0-99% | Messages being delivered |
completed | 100% | All messages processed |
cancelled | varies | Stopped before completion |
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}`);
}
{
"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 Code | Description | Recommendation |
|---|
30003 | Unreachable destination | Invalid or disconnected number |
30004 | Message blocked | Content filtered by carrier |
30005 | Unknown destination | Number doesn’t exist |
30006 | Landline destination | Cannot SMS landlines |
30007 | Carrier violation | Message rejected by carrier |
rate_limited | Too many messages | Temporary, will retry |
insufficient_balance | Out of credits | Top up balance |
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
| Event | Description |
|---|
broadcast.started | Broadcast started sending |
broadcast.completed | All messages processed |
broadcast.cancelled | Broadcast was cancelled |
Best Practices
Use appropriate polling intervals
5 seconds for small broadcasts, 30 seconds for large ones
Handle partial failures
Some contacts may fail while others succeed - check both counts
Export and analyze failures
Review failed contacts to improve your contact list quality
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");