Contacts
Add and manage individual contacts within an audience. Contacts can also be imported via CSV in the dashboard.
Add and manage individual contacts within an audience. Contacts can also be imported via CSV in the dashboard.
/audiences/:id/contactsAdd a single contact to an audience. If the email already exists in the audience the record is updated (upsert).
| Parameter | Type | Description |
|---|---|---|
emailrequired | string | Contact email address. |
first_name | string | Contact first name. |
last_name | string | Contact last name. |
properties | object | Custom properties, usable as {{key}} in a broadcast body. Replaces the stored properties — see below. |
Custom properties
Anything you store on a contact under properties becomes a variable in a broadcast, so
a contact written with { "plan": "pro" } renders {{plan}} as pro. Combined with the
fallback syntax, Hi {{first_name|there}}, you are on the {{plan|free}} plan covers the
contacts that are missing the value.
The rules are deliberately narrow, because every value ends up substituted into an email:
- Values are strings. A number or a date would need a formatting and locale decision that a merge tag has nowhere to express, so make it yourself and send the result.
- Names are lowercase letters, digits and underscores, starting with a letter, at most 40 characters. Up to 20 properties per contact, each value at most 500 characters.
email,name,first_name,last_nameandfull_nameare built in and are rejected as property names.{{email}}always resolves to the contact's real address.
POST replaces, PATCH replaces, an import merges. Sending properties to POST /audiences/:id/contacts or PATCH /audiences/:id/contacts/:contactId replaces the whole object,
which is what lets you remove a key. The batch endpoint merges instead, so a CSV carrying only
plan will not drop a company an earlier import set.
curl -X POST https://api.eusend.dev/audiences/550e8400-e29b-41d4-a716-446655440000/contacts \
-H "Authorization: Bearer eu_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"first_name": "Alice",
"properties": { "plan": "pro", "company": "Acme", "renews_on": "3 March 2026" }
}'/audiences/:id/contacts/batchUpsert up to 1,000 contacts at once. Existing contacts (matched by email) are updated; new ones are inserted. This is what the dashboard's CSV import calls.
| Parameter | Type | Description |
|---|---|---|
contactsrequired | array | Array of contact objects. Each has email (required), plus optional first_name, last_name, properties, unsubscribed, and created_at. |
contacts[].unsubscribed | boolean | Marks the contact opted out, so a list migrated from another provider keeps its unsubscribes. An import can only ADD an opt-out — see below. |
contacts[].created_at | string (ISO 8601) | Original signup time. Applied on insert only; an existing contact keeps the date it already has. |
contacts[].properties | object | Custom properties. Merged into what the contact already has, not replaced — see Custom properties above. |
An upsert only ever adds or corrects — it will not blank a field the payload does
not carry, so importing email alone will not wipe the names of the contacts it
touches.
A batch write cannot re-subscribe anyone. unsubscribed: false will not clear an existing
opt-out, and neither will omitting the field. Re-subscribing is a consent decision and lives on
PATCH /audiences/:id/contacts/:contactId, one contact at a time — so a stale spreadsheet can
never put somebody back on a list they left.
Stored contacts are capped by volume — 1,000 on Free, rising to 1,000,000 at 2M emails a month
(see plans) — counted across every audience in the organization. A write
that would exceed the cap returns 403 with code PLAN_LIMIT_EXCEEDED. See Plans &
Limits.
curl -X POST https://api.eusend.dev/audiences/550e8400-e29b-41d4-a716-446655440000/contacts/batch \
-H "Authorization: Bearer eu_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "email": "alice@example.com", "first_name": "Alice" },
{ "email": "bob@example.com", "unsubscribed": true, "created_at": "2024-02-01T10:00:00.000Z" }
]
}'/audiences/:id/contacts/batch-deleteDelete up to 1,000 contacts from an audience in one call.
| Parameter | Type | Description |
|---|---|---|
contact_idsrequired | string[] | 1 to 1,000 contact ids. Ids from another audience are ignored, not deleted. |
Returns { "deleted": n }. That count can be lower than the number of ids you sent — an
id may already be gone, or may not belong to this audience — so a retry after a dropped
response settles at 0 rather than failing.
It takes ids rather than addresses on purpose: ids come from a list you have just read, so a stale address in a script cannot delete somebody who re-subscribed under it since.
Deleting a contact is not an unsubscribe. It removes them from the audience; it does not add them
to your suppression list, and anyone already suppressed stays
suppressed. To stop mailing somebody while keeping the record, set unsubscribed on the contact
instead.
curl -X POST https://api.eusend.dev/audiences/550e8400-e29b-41d4-a716-446655440000/contacts/batch-delete -H "Authorization: Bearer eu_live_xxxxxxxxxxxx" -H "Content-Type: application/json" -d '{ "contact_ids": ["9a8b7c6d-1111-2222-3333-444455556666"] }'The rest of the contacts surface:
/audiences/:id/contactsList contacts.
/audiences/:id/contacts/:contactIdGet a contact.
/audiences/:id/contacts/:contactIdUpdate contact (name, unsubscribed, properties).
/audiences/:id/contacts/:contactIdRemove a contact.