NevarMail

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?

TaskScope
Read listslists:read
Create listslists:write
Read contactscontacts:read
Add, update or remove contacts, and add list memberscontacts: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:

storageTierWhere 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
localIn NevarMail's own storage
byo_databaseAccepted, 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:

FieldMeaning
submittedHow many addresses you sent
addedHow many were new to the list
alreadySubscribedHow 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
statusadded, 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 null clears that attribute.
  • customFields merge into the existing map rather than replacing it.
  • Send at least one of firstName, lastName or customFields; an empty update answers 400.
  • 404 means 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 toCallEffect
Erase a contact everywhereDELETE /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.

On this page