Contacts & Audiences
A contact is a recipient record: an email address with optional names, custom field values and consent data, plus a subscription status. Contacts are grouped into lists (explicit membership) and segments (membership defined by a filter), which is how you build the audience of a campaign. This page also covers custom fields, custom events, bulk import jobs, and contact deletion, erasure and export. The suppression list is separate: it blocks an address regardless of the contact record.
Contacts
Key fields
| Field | Description |
|---|---|
id |
The contact identifier. |
email |
The contact’s address; unique within your account. |
status |
Subscription status. See Contact status. |
first_name / last_name |
Optional; available in templates as {{first_name}} and {{last_name}}. |
custom |
Values keyed by custom field key, available in templates as {{custom.<key>}}. At most 200 keys, each up to 64 characters, and 64 KB in total. |
consent_source / consent_at |
Where and when consent was recorded, if you supply it. |
created_at / updated_at |
Audit timestamps. |
POST /contacts creates a contact in status subscribed. An address that already belongs to a contact, or that is on the suppression list, is refused with 409. PATCH /contacts/{id} updates first_name, last_name and custom; a custom object you send replaces the stored one as a whole, so include every key you want to keep. GET /contacts is cursor-paginated; q searches email, first name and last name without regard to case, and list_id limits the result to members of one list.
Contact status
| Status | Meaning |
|---|---|
subscribed |
The default for a new contact. Eligible for campaigns and automations. |
unsubscribed |
The contact used the unsubscribe link of a marketing message; the address is also suppressed with reason unsubscribe. Left out of campaigns and automations; transactional and OTP mail is still delivered. |
pending |
Created by an import with default_status: "pending", for example while you wait for a double opt-in confirmation. Left out of campaigns and automations only; transactional and OTP mail is still delivered. |
bounced |
Left out of campaigns and automations, like every status other than subscribed. Bounces are enforced through the suppression list (reason bounce): check the suppression list, not the contact status, to see whether an address is blocked. |
complained |
Left out of campaigns and automations, like every status other than subscribed. Spam complaints are enforced through the suppression list (reason complaint). |
Only subscribed contacts receive campaign and automation mail. Transactional and OTP sends do not look at contact status at all; they are governed by the suppression list alone.
Lifecycle
POST /contacts ─────────────────► subscribed ──(unsubscribe link)──► unsubscribed
import, default_status: pending ─► pending
import, default_status: subscribed ─► subscribed
Importing a row for an existing contact never changes that contact’s status. Deleting or erasing a contact is described in Deletion, erasure and export.
Custom fields
A custom field declares a contact attribute before you use it in templates and segment filters.
| Field | Description |
|---|---|
key |
Stable identifier, unique in your account, used as custom.<key> in templates and filters. |
label |
Display name; the only property PATCH /custom-fields/{id} changes. |
type |
string, number, date, bool or enum. The type describes the field; stored values are not converted or validated against it. |
options |
Allowed values for type: enum. |
An account can define up to 200 custom fields. Deleting a field does not remove the values already stored on contacts.
Lists and segments
Lists and segments share one shape, told apart by kind:
| Field | Description |
|---|---|
id / name |
Identifier and display name. |
kind |
static — a list, with members you add and remove; dynamic — a segment, whose members are the contacts matching filter. |
filter |
The segment’s filter tree; null for a list. |
member_count |
For a list, its current number of members. For a segment, use a preview to get the number of matching contacts. |
deleted_at |
Set when the list or segment was deleted; deleted lists and segments are not returned by the regular list and get endpoints. |
POST /lists/{id}/membersadds and removes contacts, up to 1000 ids in each ofaddandremoveper request. Contacts added this way start automations with alist_addedtrigger for that list. A suppressed contact can be a member; it is simply not mailed.DELETE /lists/{id}deletes the list and keeps its contacts.POST /lists/{id}/delete-with-contactsstarts a job that permanently deletes the contacts whose only list is this one, keeps contacts that belong to other lists, and then deletes the list; pollGET /lists/delete-jobs/{job_id}for thedeletedandkeptcounts. Neither suppresses any address.DELETE /segments/{id}deletes a segment;PATCH /segments/{id}changes its name, its filter, or both.
Segment filters
A segment filter is a tree of conditions joined by op: "and" or op: "or"; groups can be nested.
{
"op": "and",
"conditions": [
{ "field": "custom.country", "operator": "in", "value": ["DE", "FR"] },
{
"op": "or",
"conditions": [
{ "field": "custom.plan", "operator": "eq", "value": "pro" },
{ "field": "email", "operator": "contains", "value": "@example.com" }
]
}
]
}
fieldisemail,status,first_name,last_name, orcustom.<key>for a defined custom field. An unknown custom key is rejected with422when you create or update a segment or preview an unsaved filter.operatoris one ofeq,neq,in,nin,gt,lt,contains,exists.- Custom values are compared as text, whatever the field’s declared
type:gtandltcompare strings, so"10"sorts before"9". Store numbers and dates in a form that sorts correctly as text (for example zero-padded numbers and ISO 8601 dates) if you filter on ranges.
A segment has no stored membership: it is evaluated each time it is previewed or used in a campaign audience, so it always reflects the current contacts. Campaigns then keep only subscribed contacts and remove suppressed addresses.
Previewing a segment
POST /segments/preview evaluates an unsaved filter, and GET /segments/{id}/preview evaluates a saved segment. Both return:
count— the exact number of matching contacts;sample— up to 10 matching contacts;capped—truewhencountis greater than 10 000; the count itself is still exact.
Custom events
POST /events records a named event for a contact ("contact X did Y with properties Z"). name is trimmed and lower-cased and must match ^[a-z0-9_.-]{1,64}$; properties is a free-form object; occurred_at defaults to the time of the request. The contact is looked up by email exactly as it is stored, without case normalisation, and an unknown or erased contact is 404. The call always answers 201 with the event id and contact_id. Recorded events are returned in the contact’s export (GET /contacts/{id}/export).
Import jobs
POST /contacts/import accepts a CSV or XLSX file (csv as raw text, or file base64-encoded with format), a column mapping, and lawful_basis_attested: true. Limits: 5 MB of body and 50 000 data rows. The mapping must include email; first_name and last_name are the other reserved targets, and any other target is stored as a custom field value. The call returns a job_id immediately; poll GET /contacts/import/{job_id}.
Import upserts by email. A new address creates a contact with default_status (subscribed unless you choose pending). An existing contact keeps its status; non-empty names in the row replace the stored ones, and the row’s custom values are merged into the existing custom object. Suppressed addresses are skipped. With list_id, every created or updated contact is added to that static list. Imported contacts do not start automations — neither contact_created nor list_added fires for them.
| Job status | Meaning |
|---|---|
pending |
Accepted and waiting to be processed. |
processing |
Rows are being imported. |
completed |
Finished; summary gives created, updated, skipped_suppressed, invalid and per-row errors (with 1-based row numbers, the header being row 1). |
failed |
The job could not be completed. |
pending ──► processing ──► completed | failed. The list deletion job of POST /lists/{id}/delete-with-contacts follows the same four statuses; its summary carries deleted and kept, or error when it failed.
Deletion, erasure and export
DELETE /contacts/{id}permanently deletes the contact and its list memberships. The address is not suppressed and can be created or imported again. Sending history is kept.POST /contacts/{id}/eraseis the GDPR right-to-erasure operation: it replaces the contact’s personal data with a tombstone and adds the address to the suppression list, so it can never be mailed or imported again. It cannot be undone. Erased contacts no longer appear in contact searches.GET /contacts/{id}/exportis the GDPR access counterpart. It returns the contact record, its recorded custom events inevents(oldestoccurred_atfirst, at most 10 000 per response; when more remain, passevents_next_cursoras theevents_cursorquery parameter to get the next portion, and it isnullon the last one),smtp_inbound_messages(metadata of SMTP-submitted messages stored for this address — never their body, subject or recipients), and anexported_attimestamp. Delivery history is not included; read it withGET /messages?email=.
Related API
- Contacts tag —
GET /contacts,POST /contacts,GET /contacts/{id},PATCH /contacts/{id},DELETE /contacts/{id},POST /contacts/{id}/erase,GET /contacts/{id}/export,GET /contacts/{id}/lists,POST /contacts/import,GET /contacts/import/{job_id},POST /events. - Lists & Segments tag —
GET /lists,POST /lists,GET /lists/{id},DELETE /lists/{id},POST /lists/{id}/members,POST /lists/{id}/delete-with-contacts,GET /lists/delete-jobs/{job_id},GET /segments,POST /segments,GET /segments/{id},PATCH /segments/{id},DELETE /segments/{id},GET /segments/{id}/preview,POST /segments/preview. - Custom Fields tag —
GET /custom-fields,POST /custom-fields,PATCH /custom-fields/{id},DELETE /custom-fields/{id}.
See the API Reference for full schemas, and Import contacts and Segment your audience for worked examples.