# 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

```typescript
const result = await zavu.broadcasts.contacts.add(broadcast.id, {
  contacts: [
    { recipient: "+14155551234" },
    { recipient: "+14155555678" },
    { recipient: "+14155559012" },
  ],
});

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

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

### Response

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

## With Personalization

Add template variables for each contact to personalize messages:

```typescript
await zavu.broadcasts.contacts.add(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
await zavu.broadcasts.contacts.add(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
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.contacts.add(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.

## 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
{
  "added": 8,
  "duplicates": 2,
  "invalid": 0,
  "errors": []
}
```

## Handling Invalid Contacts

Invalid contacts are rejected with detailed errors:

```json
{
  "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.

## Listing Contacts

View contacts in a broadcast:

```typescript
const contacts = await zavu.broadcasts.contacts.list(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.contacts.list(broadcast.id, {
    cursor: contacts.nextCursor,
  });
}
```

```bash
curl "https://api.zavu.dev/v1/broadcasts/{broadcastId}/contacts?limit=50" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Response

```json
{
  "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
curl "https://api.zavu.dev/v1/broadcasts/{broadcastId}/contacts?status=failed" \
  -H "Authorization: Bearer $ZAVUDEV_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
// The broadcast contact id comes first; the broadcast it belongs to is a param.
await zavu.broadcasts.contacts.remove(contact.id, { broadcastId: broadcast.id });
```

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

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

- [Sending & Scheduling](https://www.zavu.dev/docs/guides/broadcasts/sending): Send your broadcast or schedule it for later
