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

# Adding Contacts

> Add recipients to your broadcast in batches

Add contacts to your broadcast before sending. Contacts can include personalization variables for customized messages.

## Adding Contacts

<CodeGroup>
  ```typescript TypeScript theme={null}
  const result = await zavu.broadcasts.addContacts(broadcast.id, {
    contacts: [
      { recipient: "+14155551234" },
      { recipient: "+14155555678" },
      { recipient: "+14155559012" },
    ],
  });

  console.log(`Added: ${result.added}`);
  console.log(`Duplicates: ${result.duplicates}`);
  console.log(`Invalid: ${result.invalid}`);
  ```

  ```python Python theme={null}
  result = zavu.broadcasts.add_contacts(broadcast.id, contacts=[
      {"recipient": "+14155551234"},
      {"recipient": "+14155555678"},
      {"recipient": "+14155559012"},
  ])

  print(f"Added: {result.added}")
  print(f"Duplicates: {result.duplicates}")
  print(f"Invalid: {result.invalid}")
  ```

  ```ruby Ruby theme={null}
  result = client.broadcasts.add_contacts(broadcast.id, contacts: [
    { recipient: "+14155551234" },
    { recipient: "+14155555678" },
    { recipient: "+14155559012" },
  ])

  puts "Added: #{result.added}"
  puts "Duplicates: #{result.duplicates}"
  puts "Invalid: #{result.invalid}"
  ```

  ```go Go theme={null}
  result, _ := client.Broadcasts.AddContacts(context.TODO(), broadcast.ID, zavudev.BroadcastAddContactsParams{
  	Contacts: []zavudev.BroadcastContactInput{
  		{Recipient: zavudev.String("+14155551234")},
  		{Recipient: zavudev.String("+14155555678")},
  		{Recipient: zavudev.String("+14155559012")},
  	},
  })

  fmt.Printf("Added: %d\n", result.Added)
  fmt.Printf("Duplicates: %d\n", result.Duplicates)
  fmt.Printf("Invalid: %d\n", result.Invalid)
  ```

  ```php PHP theme={null}
  $result = $client->broadcasts->addContacts($broadcast->id, [
      'contacts' => [
          ['recipient' => '+14155551234'],
          ['recipient' => '+14155555678'],
          ['recipient' => '+14155559012'],
      ],
  ]);

  echo "Added: {$result->added}\n";
  echo "Duplicates: {$result->duplicates}\n";
  echo "Invalid: {$result->invalid}\n";
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/broadcasts/{broadcastId}/contacts \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "contacts": [
        {"recipient": "+14155551234"},
        {"recipient": "+14155555678"},
        {"recipient": "+14155559012"}
      ]
    }'
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "added": 3,
  "duplicates": 0,
  "invalid": 0,
  "errors": []
}
```

## With Personalization

Add template variables for each contact to personalize messages:

```typescript theme={null}
await zavu.broadcasts.addContacts(broadcast.id, {
  contacts: [
    {
      recipient: "+14155551234",
      templateVariables: {
        name: "John Smith",
        order_id: "ORD-12345",
        delivery_date: "January 20th",
      },
    },
    {
      recipient: "+14155555678",
      templateVariables: {
        name: "Jane Doe",
        order_id: "ORD-12346",
        delivery_date: "January 21st",
      },
    },
  ],
});
```

The message template `Hi {{name}}, your order #{{order_id}} arrives {{delivery_date}}!` becomes:

* John: "Hi John Smith, your order #ORD-12345 arrives January 20th!"
* Jane: "Hi Jane Doe, your order #ORD-12346 arrives January 21st!"

### Per-contact URL button variables

If the template has a dynamic URL button (e.g., `https://example.com/orders/{{1}}`), supply each contact's value via `templateButtonVariables`. Keys are the button index in the template's `buttons` array (`"0"`, `"1"`, `"2"`).

```typescript theme={null}
await zavu.broadcasts.addContacts(broadcast.id, {
  contacts: [
    {
      recipient: "+14155551234",
      templateVariables: { "1": "John Smith" },
      templateButtonVariables: { "0": "ORD-12345" },
    },
    {
      recipient: "+14155555678",
      templateVariables: { "1": "Jane Doe" },
      templateButtonVariables: { "0": "ORD-12346" },
    },
  ],
});
```

When a contact omits `templateButtonVariables`, the broadcast falls back to the defaults defined on the broadcast itself.

## Batch Processing

Add contacts in batches of up to 1,000 per request. For larger lists, make multiple requests:

```typescript theme={null}
const BATCH_SIZE = 1000;

async function addAllContacts(broadcastId: string, allContacts: Contact[]) {
  let totalAdded = 0;

  for (let i = 0; i < allContacts.length; i += BATCH_SIZE) {
    const batch = allContacts.slice(i, i + BATCH_SIZE);

    const result = await zavu.broadcasts.addContacts(broadcastId, {
      contacts: batch.map(c => ({
        recipient: c.phone,
        templateVariables: { name: c.name },
      })),
    });

    totalAdded += result.added;
    console.log(`Batch ${Math.floor(i / BATCH_SIZE) + 1}: Added ${result.added} contacts`);
  }

  return totalAdded;
}
```

## Recipient Formats

| Channel  | Format        | Example            |
| -------- | ------------- | ------------------ |
| SMS      | E.164 phone   | `+14155551234`     |
| WhatsApp | E.164 phone   | `+14155551234`     |
| Email    | Email address | `user@example.com` |

<Warning>
  Phone numbers must be in E.164 format (starting with `+` and country code). Invalid formats will be rejected.
</Warning>

## Handling Duplicates

The API automatically deduplicates contacts:

* **Within request**: If the same recipient appears twice in one request, only one is added
* **Across requests**: If a recipient is already in the broadcast, they won't be added again

```json theme={null}
{
  "added": 8,
  "duplicates": 2,
  "invalid": 0,
  "errors": []
}
```

## Handling Invalid Contacts

Invalid contacts are rejected with detailed errors:

```json theme={null}
{
  "added": 7,
  "duplicates": 0,
  "invalid": 3,
  "errors": [
    { "recipient": "invalid-phone", "reason": "Invalid phone number format" },
    { "recipient": "+1234", "reason": "Phone number too short" },
    { "recipient": "not-an-email", "reason": "Invalid email format" }
  ]
}
```

<Tip>
  Validate phone numbers before adding them to avoid high rejection rates. Use E.164 format: `+[country code][number]` with no spaces or dashes.
</Tip>

## Listing Contacts

View contacts in a broadcast:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const contacts = await zavu.broadcasts.listContacts(broadcast.id, {
    limit: 50,
  });

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

  // Paginate through all contacts
  if (contacts.nextCursor) {
    const nextPage = await zavu.broadcasts.listContacts(broadcast.id, {
      cursor: contacts.nextCursor,
    });
  }
  ```

  ```python Python theme={null}
  contacts = zavu.broadcasts.list_contacts(broadcast.id, limit=50)

  for contact in contacts.items:
      print(f"{contact.recipient}: {contact.status}")

  # Paginate through all contacts
  if contacts.next_cursor:
      next_page = zavu.broadcasts.list_contacts(
          broadcast.id, cursor=contacts.next_cursor
      )
  ```

  ```ruby Ruby theme={null}
  contacts = client.broadcasts.list_contacts(broadcast.id, limit: 50)

  contacts.items.each do |contact|
    puts "#{contact.recipient}: #{contact.status}"
  end

  # Paginate through all contacts
  if contacts.next_cursor
    next_page = client.broadcasts.list_contacts(
      broadcast.id, cursor: contacts.next_cursor
    )
  end
  ```

  ```go Go theme={null}
  contacts, _ := client.Broadcasts.ListContacts(context.TODO(), broadcast.ID, zavudev.BroadcastListContactsParams{
  	Limit: zavudev.Int(50),
  })

  for _, contact := range contacts.Items {
  	fmt.Printf("%s: %s\n", contact.Recipient, contact.Status)
  }

  // Paginate through all contacts
  if contacts.NextCursor != nil {
  	nextPage, _ := client.Broadcasts.ListContacts(context.TODO(), broadcast.ID, zavudev.BroadcastListContactsParams{
  		Cursor: contacts.NextCursor,
  	})
  	_ = nextPage
  }
  ```

  ```php PHP theme={null}
  $contacts = $client->broadcasts->listContacts($broadcast->id, [
      'limit' => 50,
  ]);

  foreach ($contacts->items as $contact) {
      echo "{$contact->recipient}: {$contact->status}\n";
  }

  // Paginate through all contacts
  if ($contacts->nextCursor) {
      $nextPage = $client->broadcasts->listContacts($broadcast->id, [
          'cursor' => $contacts->nextCursor,
      ]);
  }
  ```

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

### Response

```json theme={null}
{
  "items": [
    {
      "id": "bc_abc123",
      "recipient": "+14155551234",
      "recipientType": "phone",
      "status": "pending",
      "templateVariables": { "name": "John" },
      "createdAt": "2024-01-15T10:30:00.000Z"
    },
    {
      "id": "bc_def456",
      "recipient": "+14155555678",
      "recipientType": "phone",
      "status": "pending",
      "templateVariables": { "name": "Jane" },
      "createdAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "nextCursor": "eyJpZCI6ImJjX2RlZjQ1NiJ9"
}
```

## Filtering by Status

Filter contacts by delivery status:

```bash theme={null}
curl "https://api.zavu.dev/v1/broadcasts/{broadcastId}/contacts?status=failed" \
  -H "Authorization: Bearer $ZAVU_API_KEY"
```

| Status      | Description                      |
| ----------- | -------------------------------- |
| `pending`   | Not yet sent                     |
| `queued`    | Message created, waiting to send |
| `sending`   | Currently being sent             |
| `delivered` | Successfully delivered           |
| `failed`    | Delivery failed                  |
| `skipped`   | Skipped (broadcast cancelled)    |

## Removing Contacts

Remove a contact before the broadcast is sent:

```typescript theme={null}
await zavu.broadcasts.removeContact(broadcast.id, contact.id);
```

<Warning>
  Contacts can only be removed while the broadcast is in `draft` status. Once sending begins, contacts cannot be removed.
</Warning>

## Limits

| Limit                  | Value           | Notes                                   |
| ---------------------- | --------------- | --------------------------------------- |
| Contacts per request   | 1,000           | Make multiple requests for larger lists |
| Contacts per broadcast | 1,000 (default) | Contact support to increase this limit  |
| Template variable size | 1,024 chars     | Per variable                            |

## Next Steps

<Card title="Sending & Scheduling" icon="paper-plane" href="/guides/broadcasts/sending">
  Send your broadcast or schedule it for later
</Card>
