> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zavu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracking Progress

> Monitor broadcast delivery in real-time

Track your broadcast's delivery progress with instant updates on sent, delivered, and failed messages.

## Get Progress

<CodeGroup>
  ```typescript TypeScript theme={null}
  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}`);
  ```

  ```python Python theme={null}
  progress = zavu.broadcasts.get_progress(broadcast_id)

  print(f"Status: {progress.status}")
  print(f"Progress: {progress.percent_complete}%")
  print(f"Delivered: {progress.delivered}/{progress.total}")
  print(f"Failed: {progress.failed}")
  ```

  ```ruby Ruby theme={null}
  progress = client.broadcasts.get_progress(broadcast_id)

  puts "Status: #{progress.status}"
  puts "Progress: #{progress.percent_complete}%"
  puts "Delivered: #{progress.delivered}/#{progress.total}"
  puts "Failed: #{progress.failed}"
  ```

  ```go Go theme={null}
  progress, _ := client.Broadcasts.GetProgress(context.TODO(), broadcastID)

  fmt.Printf("Status: %s\n", progress.Status)
  fmt.Printf("Progress: %v%%\n", progress.PercentComplete)
  fmt.Printf("Delivered: %d/%d\n", progress.Delivered, progress.Total)
  fmt.Printf("Failed: %d\n", progress.Failed)
  ```

  ```php PHP theme={null}
  $progress = $client->broadcasts->getProgress($broadcastId);

  echo "Status: {$progress->status}\n";
  echo "Progress: {$progress->percentComplete}%\n";
  echo "Delivered: {$progress->delivered}/{$progress->total}\n";
  echo "Failed: {$progress->failed}\n";
  ```

  ```bash cURL theme={null}
  curl https://api.zavu.dev/v1/broadcasts/{broadcastId}/progress \
    -H "Authorization: Bearer $ZAVU_API_KEY"
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "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:

```typescript theme={null}
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}!`);
```

<Tip>
  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.
</Tip>

## 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  |

## Viewing Individual Contacts

Get detailed status for each contact:

```typescript theme={null}
// 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

```json theme={null}
{
  "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:

```typescript theme={null}
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                 |

### Export Failed Contacts

```typescript theme={null}
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:

```typescript theme={null}
// 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

<Steps>
  <Step title="Use appropriate polling intervals">
    5 seconds for small broadcasts, 30 seconds for large ones
  </Step>

  <Step title="Handle partial failures">
    Some contacts may fail while others succeed - check both counts
  </Step>

  <Step title="Export and analyze failures">
    Review failed contacts to improve your contact list quality
  </Step>

  <Step title="Set up webhooks for production">
    Avoid continuous polling by using webhook notifications
  </Step>
</Steps>

## Complete Monitoring Example

```typescript theme={null}
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");
```
