Contacts
Every operation in this group: what it accepts, what it returns and the errors it can answer with.
Operations
The address book, and it belongs to the workspace rather than to whoever saved a row. Every member and every key on the workspace reads and writes the same book.
A contact is addressed by its email address on every route here, because no id is exposed. Two things write rows: somebody saving one, in the app or through this API, which lands as manual, and a member sending mail from the app composer, which lands as auto. Mail ARRIVING from an address creates nothing, so an empty book on a busy mailbox is the expected state rather than a fault.
/contacts/contacts/contacts/people/contacts/batch-delete/contacts/{email}/contacts/{email}/contacts/{email}/contacts/{email}/contacts/{email}/photo/contacts/{email}/photo/contacts/{email}/block/contacts/{email}/block/contacts/{email}/threads/contacts/{email}/activity/contacts/{email}/audiences/contacts/cards/contacts/cards/contacts/cards/{id}/contacts/cards/{id}/contacts/cards/{id}/contacts/cards/duplicates/contacts/cards/{id}/merge/contacts/birthdays/contacts/imports/contacts/imports/contacts/imports/{id}/contacts/imports/{id}/undo
GET/contacts
List contacts
The address book belongs to the workspace, so every key on it reads the same rows and a contact saved by one member is visible to the rest.
Most recently seen first, with contacts that have never been mailed last. source is manual for an address somebody saved, in the app or through this API, and auto for one recorded because a member sent mail to it from the composer. Mail arriving from an address creates no contact.
Paging is keyset. Pass nextCursor back as cursor while hasMore is true.
Requires the contacts:read scope.
Query parameters
limitintegerRows per page, a whole number from 1 to 200. The server defaults to 50.
At least 1At most 200Default50cursorstringThe
nextCursorfrom the previous page. Never build one yourself.sourcestringNarrows the page to contacts recorded that way.
One of"manual""auto""form"qstringSearches the name and the address. When nothing matches exactly on the first page, close spellings are returned instead, and the pages that follow keep matching the same way.
Up to 200 characters
Returns
Contacts in this workspace.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
nextmail.contacts.list()nextmail.contacts.listAll()nextmail.contacts.iterate()- Python
contacts.list()contacts.list_all()contacts.iterate()- Ruby
contacts.listcontacts.list_allcontacts.iterate- PHP
contacts->listcontacts->listAllcontacts->iterate- Go
Contacts.ListContacts.ListAllContacts.Iterate- Java
contacts().listcontacts().listAllcontacts().iterate- C#
Contacts.ListAsyncContacts.ListAllAsyncContacts.IterateAsync- CLI
openemail contacts list- MCP
listContacts
POST/contacts
Create a contact
The address is the identity, so there is no id to choose and no id in the response. It is trimmed and lower cased before it is stored, and the contact is saved with source set to manual.
The new contact joins the built-in default audience as it is written, and any audience named in audienceIds alongside it. Naming audiences also requires the audiences:write scope.
An address already in the book is refused rather than merged, so a retry cannot overwrite a name somebody edited in the app.
Requires the contacts:write scope.
Request body
emailstringRequiredTrimmed and lower cased before it is stored.
Up to 320 charactersFormatemailnamestringDisplay name. Leave it out to save the contact without one.
Can be nullUp to 200 charactersnotesstringFree text kept with the contact and shown beside it in the app.
Can be nullUp to 5000 charactersattributesRecord<string, string>Custom fields to set, as an object of keys and text values. A key starts with a letter and holds up to 64 letters, digits and underscores, and a value is text of at most 500 characters, trimmed before it is stored.
nullor an empty string removes a field, and a field you leave out keeps its value. A contact holds at most 50. A key or value that breaks a rule, or a 51st field, is 422invalid_contactwithparamset toattributes.<key>orattributes, and nothing is written.audienceIdsstring[]Audiences to put the new contact in. The default audience is joined whether or not it is named here. Sending this also requires the
audiences:writescope.Up to 25 items
Returns
Created.
Errors
- 409
contact_exists: that address is already in this workspace book. PATCH it instead.- 422
invalid_contactwhen the address is not one or a custom field inattributesbreaks a rule, withparamnaming the field.
The errors every operation can return400401403404429500Error catalog
Also available in
GET/contacts/people
List people
Everyone on the Contacts page in the app: the saved contacts, and every address seen in mail as the sender or a recipient of a thread's newest message, with the number of threads and when mail last moved. A person seen in mail and saved is one row.
The addresses seen in mail are listed only when the key also holds threads:read, because they are read out of the mail. Without it the rows are the saved contacts alone and seen is false. A key limited to particular addresses or domains gets the saved contacts alone too, with seen false, even when it holds threads:read, because the other addresses would be read out of the mail of every address in the workspace. Deleted addresses and the mailbox's own addresses are left out.
blockedBy names the workspace blocklist rule that blocks a row, the same rule blocked=true filters on.
Requires the contacts:read scope.
Query parameters
limitintegerRows per page, 1 to 100.
At least 1At most 100Default25cursorstringAn opaque cursor from the previous page, with the same
sort,qandblocked. Keyset, not offset: pass the previous page'snextCursor. One that names nothing in this list is a 400invalid_cursor.sortstringrecentputs the newest mail first, then saved contacts never seen in mail.namegoes by name, or by address where there is none, ignoring case.threadsputs the people with the most threads first.One of"recent""name""threads"Default"recent"qstringSearches names, addresses and notes, with close spellings when nothing matches exactly.
Up to 200 charactersemailstringOne address only, matched case insensitively: the way to read one person's thread count and last mail.
Up to 320 charactersblockedstringtruefor only the people the workspace blocklist blocks, whole-domain rules included.One of"true""false"
Returns
A page of people.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
nextmail.contacts.listPeople()nextmail.contacts.listAllPeople()nextmail.contacts.iteratePeople()- Python
contacts.list_people()contacts.list_all_people()contacts.iterate_people()- Ruby
contacts.list_peoplecontacts.list_all_peoplecontacts.iterate_people- PHP
contacts->listPeoplecontacts->listAllPeoplecontacts->iteratePeople- Go
Contacts.ListPeopleContacts.ListAllPeopleContacts.IteratePeople- Java
contacts().listPeoplecontacts().listAllPeoplecontacts().iteratePeople- C#
Contacts.ListPeopleAsyncContacts.ListAllPeopleAsyncContacts.IteratePeopleAsync- CLI
openemail contacts list-people- MCP
listPeople
POST/contacts/batch-delete
Delete contacts in bulk
Deletes up to 200 addresses in one call, each the way DELETE /contacts/{email} deletes one: a saved contact goes with its notes, photo and audience memberships, and every address is hidden, so a send does not record it again. Addresses that were only seen in mail are hidden too. An entry that is not an address comes back in invalid and the rest still go through.
Requires the contacts:write scope.
Request body
emailsstring[]RequiredAddresses, matched case insensitively. A repeated address counts once.
1 to 200 items
Returns
What was deleted.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/contacts/{email}
Retrieve a contact
The book is the workspace's. The address is matched case insensitively, and it is a path segment, so URL encode it: [email protected] travels as grace%40example.com.
A 404 means only that the address is not in the book. It says nothing about whether mail has been exchanged with it.
A caller that also holds forms:read gets signUps: what this address sent through sign-up forms, with every answer.
Requires the contacts:read scope.
Path parameters
emailstringRequiredThe contact's address, matched case insensitively.
Returns
The contact.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
nextmail.contacts.get()- Python
contacts.get()- Ruby
contacts.get- PHP
contacts->get- Go
Contacts.Get- Java
contacts().get- C#
Contacts.GetAsync- CLI
openemail contacts get- MCP
getContact
PATCH/contacts/{email}
Update a contact
A partial update, never an upsert. An address not in the book is a 404.
The address itself cannot be changed: it is the identity and the path segment, so moving a contact to a new address is a delete and a create. source and lastSeenAt are the server's and are not accepted here.
Requires the contacts:write scope.
Path parameters
emailstringRequiredThe contact's address, matched case insensitively.
Request body
namestringNew display name. Null clears it.
Can be nullUp to 200 charactersnotesstringNew notes. Null clears them.
Can be nullUp to 5000 charactersattributesRecord<string, string>Custom fields to set, as an object of keys and text values. A key starts with a letter and holds up to 64 letters, digits and underscores, and a value is text of at most 500 characters, trimmed before it is stored.
nullor an empty string removes a field, and a field you leave out keeps its value. A contact holds at most 50. A key or value that breaks a rule, or a 51st field, is 422invalid_contactwithparamset toattributes.<key>orattributes, and nothing is written.
Returns
Saved.
Errors
- 422
invalid_contactwhen a custom field inattributesbreaks a rule, withparamnaming the field.
The errors every operation can return400401403404429500Error catalog
Also available in
PUT/contacts/{email}
Save a contact
Saves the address the way Add to contacts and Keep in contacts do in the app. An address that is not a contact yet becomes one, answered with 201. One recorded from a send becomes manual, and one already saved keeps what it has, answered with 200. A deleted address is brought back. The body is optional.
Requires the contacts:write scope.
Path parameters
emailstringRequiredThe address, trimmed and lower cased on the server.
Request body
namestringUp to 200 characters. Left out, the stored name is kept.
Up to 200 charactersnotesstringUp to 5,000 characters.
nullclears the notes; left out, they are kept.Can be nullUp to 5000 charactersattributesRecord<string, string>Custom fields to set, as an object of keys and text values. A key starts with a letter and holds up to 64 letters, digits and underscores, and a value is text of at most 500 characters, trimmed before it is stored.
nullor an empty string removes a field, and a field you leave out keeps its value. A contact holds at most 50. A key or value that breaks a rule, or a 51st field, is 422invalid_contactwithparamset toattributes.<key>orattributes, and nothing is written.
Returns
Already a contact, now kept as manual.
Saved as a new contact.
Errors
- 422
unknown_parameterfor any key butname,notesandattributes, orinvalid_contactwhen a custom field breaks a rule.
The errors every operation can return400401403404429500Error catalog
Also available in
DELETE/contacts/{email}
Delete a contact
Deletes somebody from the contacts the way Delete does in the app. A saved contact goes with its notes, its photo and every audience membership, the built-in default one included. The address is then hidden: it leaves GET /contacts/people, and mail sent to it from the app composer no longer records it. The address can be one that was only ever seen in mail.
No mail moves. Saving the address again, with POST /contacts or PUT /contacts/{email}, brings it back as a new contact.
Requires the contacts:write scope.
Path parameters
emailstringRequiredThe contact's address, matched case insensitively.
Returns
Deleted and hidden.
Errors
- 422
invalid_contactwhen the path is not an address.
The errors every operation can return400401403404429500Error catalog
Also available in
PUT/contacts/{email}/photo
Set a contact photo
Uploads the photo shown for the contact, replacing any there was. Send the image itself as the body, not JSON, with its type in Content-Type: image/png, image/jpeg, image/webp, image/gif. Up to 5 MB goes in. It is fitted into a 512 pixel square and stored as WebP, and an animated image keeps its first frame.
The address has to be a saved contact: save it with PUT /contacts/{email} first.
Requires the contacts:write scope.
Path parameters
emailstringRequiredThe contact's address, matched case insensitively.
Request body
Content typeimage/png, image/jpeg, image/webp, image/gif
Returns
The contact, with its new photoUrl.
Errors
- 404
contact_not_foundonemail: the address is not a saved contact.- 422
invalid_imagewhen the body is not an image of an accepted type, is too large or cannot be read.- 502
image_not_stored: the image was read but could not be stored. Try again.- 503
image_busy: the image service is saturated. Try again shortly.
The errors every operation can return400401403429500Error catalog
Also available in
DELETE/contacts/{email}/photo
Remove a contact photo
Removes the contact photo and deletes the stored image. Removing a photo from a contact that has none changes nothing.
Requires the contacts:write scope.
Path parameters
emailstringRequiredThe contact's address, matched case insensitively.
Returns
The contact, with photoUrl null.
Errors
- 404
contact_not_foundonemail: the address is not a saved contact.
The errors every operation can return400401403422429500Error catalog
Also available in
POST/contacts/{email}/block
Block a contact
Puts the address on the workspace blocklist, the one PATCH /settings edits as blockedSenders, so mail from it is refused from then on. A plus tag is dropped: blocking [email protected] blocks [email protected]. When a rule already blocks the address, a whole-domain one included, nothing is added and created is false. The address does not have to be a contact.
It needs settings:write, because it writes the blocklist rather than the contact.
Requires the settings:write scope.
Path parameters
emailstringRequiredThe address to block.
Returns
Blocked.
Errors
- 422
invalid_contactwhen the path is not an address,blocklist_entry_too_broadwhen it has fewer than two letters or numbers, or the narrowed-key refusal.capability_unsupportedonaddressAllowlist: the blocklist belongs to the whole workspace and filters the mail of every address in it, so a key limited to particular addresses or domains cannot change it. Use a key with no address or domain restriction.
The errors every operation can return400401403404429500Error catalog
Also available in
DELETE/contacts/{email}/block
Unblock a contact
Takes every workspace blocklist rule that blocks the address off the list, and lists them in removed. When one is a whole-domain rule, everybody at that domain is unblocked with it. Rules set for one address or one domain in the settings are not touched. An address no rule blocks answers with removed empty.
Requires the settings:write scope.
Path parameters
emailstringRequiredThe address to unblock.
Returns
Unblocked.
Errors
- 422
invalid_contactwhen the path is not an address, or the narrowed-key refusal.capability_unsupportedonaddressAllowlist: the blocklist belongs to the whole workspace and filters the mail of every address in it, so a key limited to particular addresses or domains cannot change it. Use a key with no address or domain restriction.
The errors every operation can return400401403404429500Error catalog
Also available in
GET/contacts/{email}/threads
List conversations with a contact
Every thread the address wrote or was written to, in every folder, the Mail tab on a contact in the app. The address does not have to be a saved contact. Each row is a summary of the thread; GET /threads/{id} reads the messages.
Requires the threads:read scope.
Path parameters
emailstringRequiredThe address. It does not have to be a saved contact.
Query parameters
limitintegerThreads per page, 1 to 100.
At least 1At most 100Default25cursorstringThe previous page's
nextCursor, with the sameqandsort.qstringSearches inside those threads, with the same syntax as the mailbox search.
Up to 200 characterssortstringnewest(the default),oldest,senderorsubject.One of"newest""oldest""sender""subject"Default"newest"
Returns
A page of threads.
Errors
- 422
invalid_contactwhen the path is not an address, or the narrowed-key refusal.capability_unsupportedonaddressAllowlist: a contact's threads and activity are read from the mail of every address in the workspace, so a key limited to particular addresses or domains cannot read them. Use a key with no address or domain restriction.
The errors every operation can return400401403404429500Error catalog
Also available in
- TypeScript
nextmail.contacts.listThreads()nextmail.contacts.listAllThreads()nextmail.contacts.iterateThreads()- Python
contacts.list_threads()contacts.list_all_threads()contacts.iterate_threads()- Ruby
contacts.list_threadscontacts.list_all_threadscontacts.iterate_threads- PHP
contacts->listThreadscontacts->listAllThreadscontacts->iterateThreads- Go
Contacts.ListThreadsContacts.ListAllThreadsContacts.IterateThreads- Java
contacts().listThreadscontacts().listAllThreadscontacts().iterateThreads- C#
Contacts.ListThreadsAsyncContacts.ListAllThreadsAsyncContacts.IterateThreadsAsync- CLI
openemail contacts list-threads
GET/contacts/{email}/activity
Read activity with a contact
The Activity tab on a contact in the app: messages received from the address and sent to it, per bucket, over a window that ends now, with the threads that moved, the threads waiting on a reply from the mailbox, and the median time each side takes to answer. Mail in the bin or in spam is left out.
Requires the threads:read scope.
Path parameters
emailstringRequiredThe address. It does not have to be a saved contact.
Query parameters
minutesintegerHow far back the window reaches from now. The default is 90 days.
At least 1At most 10540800Default129600grainstringBucket width:
minute,hourorday, defaulting today.One of"minute""hour""day"Default"day"offsetMinutesintegerThe reader's offset from UTC in minutes, so that a day bucket falls on their calendar.
At least -840At most 840Default0
Returns
The activity.
Errors
- 422
invalid_contactwhen the path is not an address, or the narrowed-key refusal.capability_unsupportedonaddressAllowlist: a contact's threads and activity are read from the mail of every address in the workspace, so a key limited to particular addresses or domains cannot read them. Use a key with no address or domain restriction.
The errors every operation can return400401403404429500Error catalog
Also available in
PUT/contacts/{email}/audiences
Set a contact's audiences
Sets exactly which audiences the contact is in, the way the audience picker on a contact does in the app. The contact joins every listed audience it is not in yet and leaves every other one, in one transaction. The built-in default audience is always kept, so { "audienceIds": [] } leaves the contact in the default audience alone. Memberships it keeps also keep their addedAt.
The address is matched case insensitively and has to be a contact already. Create it with POST /contacts, which takes audienceIds too. An id that names no audience in this workspace refuses the whole call and changes nothing.
audiences:write is the only scope checked, because this writes memberships rather than the contact. Sending the same list again changes nothing, so a retry is safe.
Requires the audiences:write scope.
Path parameters
emailstringRequiredThe contact's address, matched case insensitively.
Request body
audienceIdsstring[]RequiredEvery audience the contact should be in once the call returns, up to 100. The default audience is kept whether or not it is named, so an empty array leaves only the default one. A repeated id counts once.
Up to 100 items
Returns
The contact, with the audiences it is in after the change.
Errors
- 404
contact_not_foundonemailwhen the address is not in the workspace book, oraudience_not_foundonaudienceIdswhen an id names no audience here. Nothing is changed.- 422
invalid_parameteronaudienceIdsfor more than 100 ids, and on the entry, such asaudienceIds.0, for an empty one,unknown_parameterfor any other key in the body.
The errors every operation can return400401403429500Error catalog
Also available in
GET/contacts/cards
List contact cards
The address book in full: every saved contact as a card with its phone numbers, postal addresses, organisation, birthday and other details, plus the cards that have no email address, such as a plumber saved on a phone. It is the same address book phones and computers sync over CardDAV.
Newest first. Pass email for the card of one saved contact, or withoutEmail=true for only the cards with no address. An address that was only ever mailed, and never saved, has no card. Paging is keyset: pass nextCursor back as cursor while hasMore is true.
Requires the contacts:read scope.
Query parameters
limitintegerCards per page, 1 to 200. 50 when left out.
At least 1At most 200cursorstringThe
nextCursorfrom the previous page. One that names no card is a 400invalid_cursor.1 to 64 charactersemailstringOnly the card of the saved contact with this address.
1 to 320 characterswithoutEmailstringtruelists only the cards that have no email address.One of"true""false"kindstringgrouplists only the groups,individualonly the people and companies.One of"individual""group"groupIdstringOnly the cards in the group with this id.
1 to 64 characters
Returns
One page of cards.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
nextmail.contacts.listCards()nextmail.contacts.listAllCards()nextmail.contacts.iterateCards()- Python
contacts.list_cards()contacts.list_all_cards()contacts.iterate_cards()- Ruby
contacts.list_cardscontacts.list_all_cardscontacts.iterate_cards- PHP
contacts->listCardscontacts->listAllCardscontacts->iterateCards- Go
Contacts.ListCardsContacts.ListAllCardsContacts.IterateCards- Java
contacts().listCardscontacts().listAllCardscontacts().iterateCards- C#
Contacts.ListCardsAsyncContacts.ListAllCardsAsyncContacts.IterateCardsAsync- CLI
openemail contacts list-cards- MCP
getContactCardlistContactCards
POST/contacts/cards
Create a contact card
Adds a card to the address book. With email it is a saved contact as well, the same one POST /contacts makes, and an address the workspace already knows from mail becomes saved. Without one it is a card on its own, kept for its name, phone numbers and the rest. A card needs at least a name, an email address, a phone number or an organisation.
Requires the contacts:write scope.
Request body
emailstringThe address Nextmail knows the contact by. Give it to a card that has none and the card becomes a saved contact. A saved contact keeps its address, so put any other address in emails.
Can be nullUp to 320 charactersFormatemailnamestringThe full name the address book shows.
Can be nullUp to 200 charactersnamePartsobjectThe name in parts, the way phones keep it. Left out, phones split name themselves.
Can be nullprefixstringA title before the name, such as Dr.
Can be nullUp to 200 charactersDefaultnullgivenstringThe first name.
Can be nullUp to 200 charactersDefaultnullmiddlestringAny middle names.
Can be nullUp to 200 charactersDefaultnullfamilystringThe surname.
Can be nullUp to 200 charactersDefaultnullsuffixstringAnything after the name, such as Jr.
Can be nullUp to 200 charactersDefaultnull
nicknamestringA nickname.
Can be nullUp to 200 charactersorganizationstringThe company or organisation.
Can be nullUp to 200 charactersdepartmentstringThe department within the organisation.
Can be nullUp to 200 characterstitlestringThe job title.
Can be nullUp to 200 charactersbirthdaystringYYYY-MM-DD, or --MM-DD when the year is not known.
Can be nullPattern^(?:\d{4}-|--)(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$notesstringFree-form notes. On a saved contact these are the notes the contact page shows.
Can be nullUp to 5000 charactersemailsobject[]Other email addresses, besides email. Given, it replaces the whole list.
Up to 50 itemsvaluestringRequiredThe email address.
Up to 320 charactersFormatemaillabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnull
phonesobject[]Phone numbers. Given, it replaces the whole list.
Up to 50 itemsvaluestringRequiredThe number or the link.
1 to 320 characterslabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnull
addressesobject[]Postal addresses. Given, it replaces the whole list.
Up to 50 itemslabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnullstreetstringThe street and house number, on as many lines as it needs.
Can be nullUp to 200 charactersDefaultnulllocalitystringThe city or town.
Can be nullUp to 200 charactersDefaultnullregionstringThe state, county or province.
Can be nullUp to 200 charactersDefaultnullpostalCodestringThe postal code.
Can be nullUp to 200 charactersDefaultnullcountrystringThe country.
Can be nullUp to 200 charactersDefaultnull
urlsobject[]Websites and profile links. Given, it replaces the whole list.
Up to 50 itemsvaluestringRequiredThe number or the link.
1 to 320 characterslabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnull
kindstringindividualfor a person or a company,groupfor a list of other cards, the groups phones keep.One of"individual""group"membersstring[]The ids of the cards in the group. Only a group takes it, and a list you send replaces the whole list.
Up to 2000 items
Returns
The new card.
Errors
- 409
contact_card_email_taken: another card already has that address.contact_card_limit: the workspace keeps as many cards as it may.- 422
invalid_contact_card: the card has nothing to keep, or a field is not valid.
The errors every operation can return400401403404429500Error catalog
Also available in
GET/contacts/cards/{id}
Get a contact card
One card with everything it holds.
Requires the contacts:read scope.
Path parameters
idstringRequiredThe card id from
GET /contacts/cards.
Returns
The card.
Errors
- 404
contact_card_not_found: no card has that id.
The errors every operation can return400401403422429500Error catalog
Also available in
PATCH/contacts/cards/{id}
Update a contact card
Changes the fields you send and leaves the rest alone. null clears a field, and a list you send replaces the whole list. On a saved contact, name and notes are the contact's own, so the contact page shows the change. email can only be given to a card that has none, which makes it a saved contact: a saved contact keeps its address, so add another address to emails instead. Phones and computers that sync the address book over CardDAV get the change on their next sync.
Requires the contacts:write scope.
Path parameters
idstringRequiredThe card id from
GET /contacts/cards.
Request body
emailstringThe address Nextmail knows the contact by. Give it to a card that has none and the card becomes a saved contact. A saved contact keeps its address, so put any other address in emails.
Can be nullUp to 320 charactersFormatemailnamestringThe full name the address book shows.
Can be nullUp to 200 charactersnamePartsobjectThe name in parts, the way phones keep it. Left out, phones split name themselves.
Can be nullprefixstringA title before the name, such as Dr.
Can be nullUp to 200 charactersDefaultnullgivenstringThe first name.
Can be nullUp to 200 charactersDefaultnullmiddlestringAny middle names.
Can be nullUp to 200 charactersDefaultnullfamilystringThe surname.
Can be nullUp to 200 charactersDefaultnullsuffixstringAnything after the name, such as Jr.
Can be nullUp to 200 charactersDefaultnull
nicknamestringA nickname.
Can be nullUp to 200 charactersorganizationstringThe company or organisation.
Can be nullUp to 200 charactersdepartmentstringThe department within the organisation.
Can be nullUp to 200 characterstitlestringThe job title.
Can be nullUp to 200 charactersbirthdaystringYYYY-MM-DD, or --MM-DD when the year is not known.
Can be nullPattern^(?:\d{4}-|--)(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$notesstringFree-form notes. On a saved contact these are the notes the contact page shows.
Can be nullUp to 5000 charactersemailsobject[]Other email addresses, besides email. Given, it replaces the whole list.
Up to 50 itemsvaluestringRequiredThe email address.
Up to 320 charactersFormatemaillabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnull
phonesobject[]Phone numbers. Given, it replaces the whole list.
Up to 50 itemsvaluestringRequiredThe number or the link.
1 to 320 characterslabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnull
addressesobject[]Postal addresses. Given, it replaces the whole list.
Up to 50 itemslabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnullstreetstringThe street and house number, on as many lines as it needs.
Can be nullUp to 200 charactersDefaultnulllocalitystringThe city or town.
Can be nullUp to 200 charactersDefaultnullregionstringThe state, county or province.
Can be nullUp to 200 charactersDefaultnullpostalCodestringThe postal code.
Can be nullUp to 200 charactersDefaultnullcountrystringThe country.
Can be nullUp to 200 charactersDefaultnull
urlsobject[]Websites and profile links. Given, it replaces the whole list.
Up to 50 itemsvaluestringRequiredThe number or the link.
1 to 320 characterslabelstringWhat the entry is for: home, work, mobile, main, fax, pager or other.
One of"home""work""mobile""main""fax""pager""other"Default"other"customLabelstringA label of your own, such as Assistant. Address books that support it show it instead of label.
Can be nullUp to 60 charactersDefaultnull
kindstringindividualfor a person or a company,groupfor a list of other cards, the groups phones keep.One of"individual""group"membersstring[]The ids of the cards in the group. Only a group takes it, and a list you send replaces the whole list.
Up to 2000 items
Returns
The card as it is now.
Errors
- 404
contact_card_not_found: no card has that id.- 409
contact_card_email_taken: another card already has that address.- 422
invalid_contact_card: a field is not valid, or the call tries to change the address of a saved contact.
The errors every operation can return400401403429500Error catalog
Also available in
DELETE/contacts/cards/{id}
Delete a contact card
Removes the card from the address book, and from every phone and computer that syncs it. On a saved contact this is the same as DELETE /contacts/{email}: the contact goes with its notes, photo and audiences, and the address is hidden from the people list and the suggestions.
Requires the contacts:write scope.
Path parameters
idstringRequiredThe card id from
GET /contacts/cards.
Returns
The card is gone.
Errors
- 404
contact_card_not_found: no card has that id.
The errors every operation can return400401403422429500Error catalog
Also available in
GET/contacts/cards/duplicates
Find duplicate contact cards
Cards that look like the same person: they share an email address or a phone number, or have the same full name. Each group lists its cards with the most complete one first, the one to keep when you merge them with POST /contacts/cards/{id}/merge. Groups found by a shared address come first, then by phone number, then by name. Groups of cards are never compared.
Requires the contacts:read scope.
Returns
The groups of cards that look like one person.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
nextmail.contacts.findDuplicateCards()- Python
contacts.find_duplicate_cards()- Ruby
contacts.find_duplicate_cards- PHP
contacts->findDuplicateCards- Go
Contacts.FindDuplicateCards- Java
contacts().findDuplicateCards- C#
Contacts.FindDuplicateCardsAsync- CLI
openemail contacts find-duplicate-cards- MCP
findDuplicateContacts
POST/contacts/cards/{id}/merge
Merge contact cards
Folds the other cards into this one and deletes them. Fields this card leaves empty are filled from the others, and every email address, phone number, postal address and link is kept. Groups that held the others hold this card instead. A saved contact among the others stays known by its address, in the people list and in its audiences, but it leaves the address book. Phones and computers that sync the address book over CardDAV get the change on their next sync.
Requires the contacts:write scope.
Path parameters
idstringRequiredThe card id from
GET /contacts/cards.
Request body
cardIdsstring[]RequiredThe cards to fold into this one. They are deleted once their details are kept.
1 to 10 items
Returns
The card that kept everything.
Errors
- 404
contact_card_not_found: no card has one of those ids.- 422
invalid_contact_card: the list names the card itself, or a group.
The errors every operation can return400401403429500Error catalog
Also available in
GET/contacts/birthdays
List birthdays
The birthdays of the people in the address book that fall between from and to, one entry for each date, with their age when the card holds the year of birth. Someone born on 29 February has their birthday on 28 February in other years. It is the birthdays calendar the Calendar page of the app can show. from and to are ISO 8601 with a zone, at most a year apart, and timezone, UTC unless you say, decides which day the window starts and ends on.
Requires the contacts:read scope.
Query parameters
fromstringStart of the window.
tostringEnd of the window, after
fromand at most 366 days later.timezonestringIANA zone such as
Europe/London. Defaults toUTC.1 to 64 charactersDefault"UTC"
Returns
Every birthday in the window, sorted by date and then by name.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/contacts/imports
List contacts imports
The contacts files and address books brought in, newest first, each with what it added and what it left out. Paging is keyset: pass nextCursor back as cursor while hasMore is true.
Requires the contacts:read scope.
Query parameters
limitintegerRows per page, 1 to 100.
At least 1At most 100Default25cursorstringThe previous page's
nextCursor, passed back as it came. It is opaque: it holds where the last row sat in this list's order, so a row deleted or edited between pages never breaks the walk, and the next page starts at the first row that sorts after it. A value this list did not hand out is a 400invalid_cursor.
Returns
One page of contacts imports.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
nextmail.contacts.listImports()nextmail.contacts.listAllImports()nextmail.contacts.iterateImports()- Python
contacts.list_imports()contacts.list_all_imports()contacts.iterate_imports()- Ruby
contacts.list_importscontacts.list_all_importscontacts.iterate_imports- PHP
contacts->listImportscontacts->listAllImportscontacts->iterateImports- Go
Contacts.ListImportsContacts.ListAllImportsContacts.IterateImports- Java
contacts().listImportscontacts().listAllImportscontacts().iterateImports- C#
Contacts.ListImportsAsyncContacts.ListAllImportsAsyncContacts.IterateImportsAsync- CLI
openemail contacts list-imports- MCP
listContactImports
POST/contacts/imports
Import a contacts file
Takes a vCard (.vcf) file as the request body and queues it. Every card in it becomes a contact card, with its phone numbers, addresses and the rest. Poll GET /contacts/imports/{id} until status is completed.
The file may be 20 MB. Imported contacts join no audience, so a broadcast never reaches them because of an import. A card whose address or UID is already in the address book is counted under merged and left as it is, and a card with no address is matched on its phone number. A workspace keeps 20,000 cards, and the cards past that are counted under skipped.
Requires the contacts:write scope.
Query parameters
filenamestringThe name to show for the file in the list of imports.
Up to 255 characters
Request body
Content typetext/vcard, application/octet-stream
Returns
The import, with status queued.
Errors
- 400
not_a_contacts_file: the body holds no vCard.item_import_empty: the body is empty.- 413
item_import_too_large: the file is over 20 MB.
The errors every operation can return401403404422429500Error catalog
Also available in
GET/contacts/imports/{id}
Get a contacts import
One contacts import with its counts and its report: the files it read, how many cards it left out and why, and up to 20 of the cards it left out.
Requires the contacts:read scope.
Path parameters
idstringRequiredContacts import id,
iimp_followed by 24 hex characters.
Returns
The import.
Errors
- 404
item_import_not_found: no such import.
The errors every operation can return400401403422429500Error catalog
Also available in
POST/contacts/imports/{id}/undo
Remove what a contacts import added
Deletes every contact and card the import added, changed since or not, and marks the import undone. Contacts that were already in the address book are not touched.
Requires the contacts:write scope.
Path parameters
idstringRequiredContacts import id,
iimp_followed by 24 hex characters.
Returns
The import, with status undone.
Errors
- 404
item_import_not_found: no such import.- 409
item_import_not_undoable: it is still running or was already removed.
The errors every operation can return400401403422429500Error catalog
Also available in
Objects
Contactobject
objectstring- One of
"contact" emailstringTrimmed and lower cased on write. This is the key every contact route takes, because no contact id is exposed.
namestring- Can be null
sourcestringautowhen a send from the app composer recorded the address,manualwhen somebody saved it here or in the app. A recorded send never downgrades amanualcontact toauto.One of"manual""auto""form"notesstring- Can be null
attributesRecord<string, string>The custom fields of the contact, as an object of keys and text values, at most 50 of them. Empty when it has none. An automation reads one as
attributes.<key>, in an email value, a branch or a trigger filter, and itsupdate_fieldstep writes one.photoUrlstringWhere the contact photo is served, or null when the contact has none. Set it with
PUT /contacts/{email}/photo. A new upload gets a new URL.Can be nulllastSeenAtstringWhen mail last went to this address from the app composer. Null until it does.
Can be nullFormatdate-timecreatedAtstring- Format
date-time updatedAtstring- Format
date-time
ContactActivityobject
objectstring- One of
"contact_activity" emailstringsincestringThe start of the first bucket, on the boundary
grainandoffsetMinutesput it.Formatdate-timeuntilstring- Format
date-time grainstring- One of
"minute""hour""day" bucketsobject[]Only the buckets with mail in them, oldest first.
bucketstringThe bucket in the reader's time:
2026-09-23,2026-09-23T14or2026-09-23T14:05.receivedintegerMessages from this address.
sentintegerMessages from the mailbox to this address.
totalsobjectreceivedintegersentintegerthreadsintegerThreads with at least one message in the window.
waitingintegerThreads whose newest message is from this address, so the mailbox owes the reply.
lastReceivedAtstring- Can be nullFormat
date-time lastSentAtstring- Can be nullFormat
date-time
replyTimeobjectyoursnumberMedian milliseconds the mailbox took to answer, or null without a reply to measure.
Can be nulltheirsnumberMedian milliseconds this address took to answer.
Can be null
ContactBatchDeleteobject
objectstring- One of
"contact_batch_delete" deletedintegerAddresses deleted and hidden, saved or not.
savedintegerOf those, how many were saved contacts.
invalidstring[]Entries that are not addresses, lower cased. Nothing happened to them.
ContactBirthdayobject
objectstringRequired- One of
"contact_birthday" cardIdstringRequiredThe card the birthday is on.
contactIdstringRequiredThe saved contact the card belongs to, or null for a card with no address.
Can be nullemailstringRequiredThe address of that saved contact.
Can be nullnamestringRequiredThe name on the card, or its organisation.
Can be nullbirthdaystringRequiredThe birthday as the card holds it: YYYY-MM-DD, or --MM-DD when the year is not known.
datestringRequiredThe day in the window the birthday falls on.
FormatdateageintegerRequiredHow old they turn that day, or null when the year of birth is not known.
Can be nullphotoUrlstringRequiredWhere the contact photo is served, or null when there is none.
Can be null
ContactBirthdayListobject
objectstring- One of
"list"
ContactBlockobject
objectstring- One of
"contact_block" emailstringThe address as the blocklist sees it: lower cased, with any plus tag dropped.
blockedbooleanblockedByobjectOn a block, the rule that now blocks the address.
Can be nullrulestringThe entry on the workspace blocklist that matches this address, exactly as it is stored.
liststringblockedSendersfor an address or pattern rule,blockedDomainsfor a rule that blocks a whole domain and every subdomain under it.One of"blockedSenders""blockedDomains"
createdbooleanOn a block, false when a rule already blocked the address and nothing was added.
removedobject[]On an unblock, every rule taken off the workspace blocklist. A
blockedDomainsentry here unblocked the whole domain.rulestringThe entry on the workspace blocklist that matches this address, exactly as it is stored.
liststringblockedSendersfor an address or pattern rule,blockedDomainsfor a rule that blocks a whole domain and every subdomain under it.One of"blockedSenders""blockedDomains"
ContactCardobject
objectstringRequired- One of
"contact_card" idstringRequireduidstringRequiredThe vCard UID that phones and computers know the card by.
kindstringRequiredindividualfor a person or a company,groupfor a list of other cards, the groups phones keep.One of"individual""group"membersstring[]RequiredFor a group, the ids of the cards in it. Empty for everyone else.
emailstringRequiredThe address of the saved contact this card belongs to, the one
/contacts/{email}takes. null for a card with no address.Can be nullnamestringRequiredThe full name.
Can be nullnamePartsobjectRequiredThe name in parts, when somebody set them or a phone sent them.
Can be nullprefixstringRequiredA title before the name, such as Dr.
Can be nullgivenstringRequiredThe first name.
Can be nullmiddlestringRequiredAny middle names.
Can be nullfamilystringRequiredThe surname.
Can be nullsuffixstringRequiredAnything after the name, such as Jr.
Can be null
nicknamestringRequiredA nickname.
Can be nullorganizationstringRequiredThe company or organisation.
Can be nulldepartmentstringRequiredThe department within the organisation.
Can be nulltitlestringRequiredThe job title.
Can be nullbirthdaystringRequiredYYYY-MM-DD, or --MM-DD when the year is not known.
Can be nullnotesstringRequiredFree-form notes.
Can be nullemailsContactCardValue[]RequiredOther email addresses, besides email.
phonesContactCardValue[]RequiredaddressesContactCardAddress[]RequiredurlsContactCardValue[]RequiredWebsites and profile links.
photoUrlstringRequiredWhere the contact photo is served, or null when there is none.
Can be nullcreatedAtstringRequired- Format
date-time updatedAtstringRequired- Format
date-time
ContactCardAddressobject
labelstringRequiredWhat the entry is for.
One of"home""work""mobile""main""fax""pager""other"customLabelstringRequiredA label of your own, such as Assistant, shown instead of label where the address book supports it.
Can be nullstreetstringRequiredThe street and house number.
Can be nulllocalitystringRequiredThe city or town.
Can be nullregionstringRequiredThe state, county or province.
Can be nullpostalCodestringRequiredThe postal code.
Can be nullcountrystringRequiredThe country.
Can be null
ContactCardDuplicateGroupobject
objectstringRequired- One of
"contact_card_duplicates" reasonstringRequiredThe strongest thing the cards share: an
emailaddress, aphonenumber or the fullname.One of"email""phone""name"cardsContactCard[]RequiredThe cards, the most complete one first.
ContactCardDuplicateListobject
objectstring- One of
"list" hasMorebooleanTrue when there are more than 100 groups. Merge these and ask again for the rest.
ContactCardListobject
objectstring- One of
"list" dataContactCard[]hasMorebooleanTrue when another page follows. Pass
nextCursorback ascursorto read it.nextCursorstringThe id of the last row on this page, or null on the last page.
Can be null
ContactCardValueobject
valuestringRequiredThe address, number or link.
labelstringRequiredWhat the entry is for.
One of"home""work""mobile""main""fax""pager""other"customLabelstringRequiredA label of your own, such as Assistant, shown instead of label where the address book supports it.
Can be null
ContactDetailobject
objectstring- One of
"contact" emailstringTrimmed and lower cased on write. This is the key every contact route takes, because no contact id is exposed.
namestring- Can be null
sourcestringautowhen a send from the app composer recorded the address,manualwhen somebody saved it here or in the app. A recorded send never downgrades amanualcontact toauto.One of"manual""auto""form"notesstring- Can be null
attributesRecord<string, string>The custom fields of the contact, as an object of keys and text values, at most 50 of them. Empty when it has none. An automation reads one as
attributes.<key>, in an email value, a branch or a trigger filter, and itsupdate_fieldstep writes one.photoUrlstringWhere the contact photo is served, or null when the contact has none. Set it with
PUT /contacts/{email}/photo. A new upload gets a new URL.Can be nulllastSeenAtstringWhen mail last went to this address from the app composer. Null until it does.
Can be nullFormatdate-timecreatedAtstring- Format
date-time updatedAtstring- Format
date-time audiencesobject[]Every audience this contact is in, the built-in default one included. Single-contact responses carry it; the list route does not.
idstringThe audience handle,
aud_plus 24 hex.namestringbuiltinstringdefaulton the one audience every contact joins, null on an audience somebody created.Can be nullOne of"default"
signUpsContactSignUp[]What this address sent through the workspace's sign-up forms, newest first and at most 20. Present only when the caller holds
forms:read, and an empty list when the address never signed up. The workspace owner sees every form's sign-ups, and anybody else those of the forms they made. Single-contact responses carry it; the list route does not.
ContactImportobject
objectstringRequired- One of
"contact_import" idstringRequirediimp_followed by 24 hex characters.kindstringRequired- One of
"contacts" sourcestringRequiredfileis an upload. The others came with a mailbox import:takeoutfrom a Google Takeout archive,caldavandcarddavfrom the old provider,microsoftfrom an Outlook sign-in.One of"file""takeout""caldav""carddav""microsoft"statusstringRequiredundonemeans what it brought in was removed again.One of"uploading""queued""running""completed""failed""cancelled""undone"fileNamestringRequired- Can be null
fileBytesintegerRequired- At least 0
parentImportIdstringRequiredThe mailbox import it came with, when there was one.
Can be nullcountsobjectRequiredtotalintegerRequiredItems found so far.
At least 0importedintegerRequiredItems added.
At least 0mergedintegerRequiredContacts left as they were because the address book already holds them. Always 0 on a calendar import, where an event that is already here is counted under
skipped.At least 0skippedintegerRequiredItems left out for a reason given in
report.At least 0failedintegerRequired- At least 0
reportobjectRequiredcollectionsobject[]RequiredThe calendars or address books the items came from, by their original name.
namestringRequireditemsintegerRequired- At least 0
skippedRecord<string, integer>RequiredHow many items were left out, by reason.
samplesobject[]RequiredUp to 20 of the items left out.
namestringRequiredreasonstringRequired- One of
"uid-taken""no-start""duplicate""limit""invalid""empty"
lastErrorstringRequired- Can be null
undoablebooleanRequiredWhether the undo call would remove anything.
createdAtstringRequired- Format
date-time startedAtstringRequired- Can be nullFormat
date-time finishedAtstringRequired- Can be nullFormat
date-time undoneAtstringRequired- Can be nullFormat
date-time
ContactImportListobject
objectstring- One of
"list" dataContactImport[]hasMorebooleanTrue when another page follows. Pass
nextCursorback ascursorto read it.nextCursorstringAn opaque cursor for the next page, or null on the last page. Pass it back unchanged.
Can be null
ContactListobject
objectstring- One of
"list" dataContact[]hasMorebooleannextCursorstring- Can be null
ContactSignUpobject
idstringThe submission id,
fsb_plus 24 hex.GET /forms/{id}/submissions/{submissionId}reads it whole.formIdstringThe form the person filled in,
frm_plus 24 hex.formNamestringWhat the workspace calls that form.
statusstringaddedonce the person is a contact in the audiences.pendingwhile a double opt-in form waits for them to confirm.One of"pending""added"answersContactSignUpAnswer[]A snapshot of every answer, kept as it was even after the form changes.
sourceUrlstringThe page the form was filled in on, its origin and path only, when it was known.
Can be nullcreatedAtstringWhen the person sent the form.
Formatdate-timeconfirmedAtstringWhen the person joined the audiences. Null while pending.
Can be nullFormatdate-time
ContactSignUpAnswerobject
keystringThe field key the value was posted under.
labelstringThe field label when the person signed up.
typestringThe kind of field that was answered.
One of"email""text""textarea""number""phone""url""date""select""radio""checkboxes""checkbox""consent""audiences""hidden""heading""paragraph""divider"valuestring | string[] | booleanText for most fields, a list for multiple choice, true or false for a checkbox.
displaystringThe value as a person reads it, option labels included.
ContactThreadobject
objectstring- One of
"thread" idstringThe id
GET /threads/{id}takes.subjectstring- Can be null
fromobjectWho sent the newest message.
Can be nullnamestring- Can be null
emailstring
receivedAtstringWhen the newest message arrived or went out.
Can be nullmessageCountintegerhasUnreadbooleanlabelsobject[]idstringnamestring
ContactThreadListobject
objectstring- One of
"list" dataContactThread[]hasMorebooleanTrue when another page follows. Pass
nextCursorback ascursorto read it.nextCursorstringOpaque. Pass it back as
cursorfor the next page, and never build one yourself.Can be null
DeletedContactobject
objectstring- One of
"contact" emailstringdeletedboolean- One of
true wasSavedbooleanTrue when a saved contact was removed, false when the address was only seen in mail and has now been hidden.
DeletedContactCardobject
objectstringRequired- One of
"contact_card" idstringRequireddeletedbooleanRequired- One of
true
Personobject
objectstring- One of
"person" emailstringLower cased. The key every contact route takes.
displayEmailstringThe address as the most recent message wrote it, which may carry capitals.
namestringThe saved name, or else the newest display name seen in mail.
Can be nullsavedbooleanTrue when the address is a saved contact.
sourcestringAs on a contact, or null when the address is only seen in mail.
Can be nullOne of"manual""auto""form"notesstring- Can be null
attributesRecord<string, string>The custom fields of the saved contact, as on a contact. Empty when the address is only seen in mail or has none.
photoUrlstring- Can be null
threadsintegerHow many threads have this address as the sender or a recipient of their newest message. Null when the key does not read mail or the address has never been seen in it.
Can be nulllastAtstringWhen the newest of those threads last moved. Null for a saved contact never seen in mail.
Can be nullFormatdate-timecreatedAtstring- Can be nullFormat
date-time updatedAtstring- Can be nullFormat
date-time blockedByobjectThe workspace blocklist rule that blocks this address, or null when none does.
Can be nullrulestringThe entry on the workspace blocklist that matches this address, exactly as it is stored.
liststringblockedSendersfor an address or pattern rule,blockedDomainsfor a rule that blocks a whole domain and every subdomain under it.One of"blockedSenders""blockedDomains"
PersonListobject
objectstring- One of
"list" dataPerson[]hasMorebooleanTrue when another page follows. Pass
nextCursorback ascursorto read it.nextCursorstringOpaque. Pass it back as
cursorfor the next page, and never build one yourself.Can be nullseenbooleanTrue when the addresses seen in mail are included, which needs
threads:read. False means the rows are the saved contacts alone.