Sync contacts and lists from your own system
Create lists, add subscribers, and keep contact details current from your CRM, app or database with the NevarMail lists and contacts API.
You can keep NevarMail's lists in step with your own system by calling the lists and contacts endpoints of the REST API: create a list once, then add subscribers and update contacts as people change in your CRM, app or database. Adding and updating contacts is idempotent, so a retried sync never creates duplicates. There is no call to drop a contact from one list while keeping it on others: deleting a contact removes it from every list the key can reach (a project-pinned key removes it from that project's lists only).
This page is for syncing from code. To load a one-off spreadsheet, use Import contacts from a CSV instead.
Which scopes does a sync need?
| Task | Scope |
|---|---|
| Read lists | lists:read |
| Create lists | lists:write |
| Read contacts | contacts:read |
| Add, update or remove contacts, and add list members | contacts:write |
Create the key under Settings → API Keys (Authenticate API requests). Nothing on this page sends mail, and test mode does not apply to contact writes: a Test key writes real contacts. The lists:write and contacts:* scopes are backed by the permission to configure safety settings, so the person creating the key needs a role that holds it.
Where do synced contacts live?
Each list has a storage tier, chosen when you create it:
storageTier | Where the contact data is stored today |
|---|---|
provider (the default) | On your sending provider's account, as a provider list. NevarMail keeps no copy of the members |
local | In NevarMail's own storage |
byo_database | Accepted, but today it behaves exactly like provider. Members are not yet written to the database you connect under Settings → BYO Database |
NevarMail's principle is to keep recipient data in systems you own, so provider is the right default for most syncs. Your data and recipient data explains what each store means for where addresses are held.
Step 1: create a list
POST /api/v1/lists with a name, plus projectId when your organization has more than one project (otherwise the call answers 400 PROJECT_REQUIRED). description, unsubHandler (hosted, the default, or custom_html) and storageTier are optional.
{
"name": "Product updates",
"storageTier": "provider"
}A repeat name in the same project answers 409 DUPLICATE_NAME rather than creating a second list, so you can retry the call safely. Keep the returned id; every other call on this page uses it. GET /api/v1/lists returns your lists if you lose it (a key pinned to one project sees only that project's lists).
Step 2: add subscribers
POST /api/v1/lists/{id}/contacts takes one subscriber or an array of up to 1,000 at the same URL; there is no separate bulk endpoint.
[
{ "email": "ana@example.com", "firstName": "Ana", "customFields": { "plan": "pro" } },
{ "email": "bo@example.com" }
]Only email is required. firstName, lastName and customFields (a map of string values) are optional and usable as template variables.
The response says what happened to each batch:
| Field | Meaning |
|---|---|
submitted | How many addresses you sent |
added | How many were new to the list |
alreadySubscribed | How many were already on it (counted, not rewritten). On a provider (or byo_database) list this is always 0 and added equals submitted, because the provider does not report which addresses were new |
status | added, already_subscribed, or processing when a provider-tier list finishes the add after the call returns |
Adds are idempotent, so re-running yesterday's sync is safe. Suppressed addresses are accepted here and simply never receive mail: suppression is checked at send time, not when you add someone (Bounces and suppressions).
A provider-tier list that has never synced to the provider answers 422 LIST_NOT_SYNCED; an unknown list id answers 404 NOT_FOUND.
Step 3: keep contact details current
PATCH /api/v1/contacts updates a contact everywhere your organization holds it: the provider copy and every local list membership.
{ "email": "ana@example.com", "firstName": "Ana", "lastName": null, "customFields": { "plan": "team" } }- Keys you leave out are untouched.
- An explicit
nullclears that attribute. customFieldsmerge into the existing map rather than replacing it.- Send at least one of
firstName,lastNameorcustomFields; an empty update answers400. 404means the organization has no marketing provider and the address is on no local list.
To create contacts that are not on a list yet, POST /api/v1/contacts (same shape, plus an optional listIds array). With listIds it is the same path as step 2. Without listIds the contact is stored on your marketing provider, and the call answers 404 when no marketing provider is configured; a key pinned to one project never writes the provider store, so give it listIds. GET /api/v1/contacts pages through all contacts; add q= to filter by email or name (the result is still paginated).
Step 4: remove people who opt out or leave
| You want to | Call | Effect |
|---|---|---|
| Erase a contact everywhere | DELETE /api/v1/contacts?email=… | Removed from the provider and from every local list; a project-pinned key removes from that project's lists only |
Erasing a contact is not the same as suppressing them. An unsubscribe or a "never email this person" request belongs in suppressions, which block sends across every list; see Unsubscribes and compliance.
Can an AI agent do this?
Partly. The MCP server exposes list_lists and get_list (the read side of step 1). Creating lists and adding or updating contacts is REST-only today.
- What happens if my sync runs twice?
- Nothing extra. List creation answers 409 DUPLICATE_NAME on a repeat name, contact writes are keyed by email, and adding a known address never creates a duplicate: a local list counts it as alreadySubscribed, and any other list upserts it on the provider in place.
- Can I add a suppressed address to a list?
- Yes. The add succeeds and the address sits on the list; suppression is checked at send time, so it never receives mail until the suppression is removed.
Import contacts from a CSV
Add a spreadsheet of contacts to one of your project's lists — what the file needs, where the contacts go, and how you hear when it's done.
Why didn't my email send?
The most common cause is test mode blocking an unlisted recipient — here's how to tell, plus every other condition that can block a send and what each one looks like in the response.