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

# upsert_mailing_list_recipients

> Add or update recipients on a mailing list — idempotent, keyed by external_id or email

The sync primitive: adds new contacts, updates existing ones, reactivates contacts the team removed, and never touches contacts who unsubscribed themselves. Safe to call repeatedly with the same data. Up to 500 entries per call; the public API's [`PUT /email/lists/{id}/recipients`](/api-reference/mailing-lists/upsert-recipients) takes 1000 and is the better fit for a scheduled job.

## Arguments

| Name | Type | Description |
| - | - | - |
| `mailing_list_id` | string | From [`list_mailing_lists`](/mcp/project/list-mailing-lists) |
| `recipients` | array | Email strings, or objects `{ "email", "name"?, "external_id"?, "attributes"? }` |

Send the user's own id as `external_id`: matching happens on it first, so a later email change updates the same recipient instead of creating a second one. `attributes` is a flat map of up to 20 scalar values and replaces the stored map when provided.

## How entries are resolved

| The entry… | Result |
| - | - |
| has an `external_id` already on the list | that recipient is updated (email included) |
| has an email on the list with no `external_id` | that recipient is updated and adopts the `external_id` |
| matches nothing | added |
| matches a removed recipient | reactivated |
| matches an unsubscribed recipient | left untouched, counted in `skipped_unsubscribed` |
| has an email that belongs to a different `external_id` | listed in `conflicts`, nothing changed |

## Returns

```json theme={null}
{
  "added": 3, "updated": 1, "reactivated": 0, "unchanged": 12,
  "skipped_unsubscribed": 1, "invalid": [], "conflicts": [],
  "summary": "3 added, 1 updated, 0 reactivated, 12 unchanged, 1 unsubscribed (left as is)"
}
```

## Errors

* The call would push the list past 500 active recipients — nothing is written; split the batch or remove contacts first.
* Unknown list id (or a list from another project).

## Example prompts

> "Add these people from the CSV I pasted to the Customers list, with their names."

> "Sync our new signups from this week into the release-notes list, keyed by their user id."
