---
title: "Contacts"
description: "Every operation in this group: what it accepts, what it returns and the errors it can answer with."
url: "https://nextmail.sh/docs/api/reference/contacts"
area: "API"
category: "Reference"
---

# 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.

### `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.

- Scopes: `contacts:read`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 200, default `50`): Rows per page, a whole number from 1 to 200. The server defaults to 50.
- `cursor` (`string`): The `nextCursor` from the previous page. Never build one yourself.
- `source` (`string`, one of `"manual"`, `"auto"`, `"form"`): Narrows the page to contacts recorded that way.
- `q` (`string`, up to 200 characters): Searches 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.

**Returns**

- `200` `ContactList`: Contacts in this workspace.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.list()`](https://nextmail.sh/docs/sdk/reference/contacts#list), [`nextmail.contacts.listAll()`](https://nextmail.sh/docs/sdk/reference/contacts#listAll), [`nextmail.contacts.iterate()`](https://nextmail.sh/docs/sdk/reference/contacts#iterate); Python [`contacts.list()`](https://nextmail.sh/docs/python/reference/contacts#list), [`contacts.list_all()`](https://nextmail.sh/docs/python/reference/contacts#listAll), [`contacts.iterate()`](https://nextmail.sh/docs/python/reference/contacts#iterate); Ruby [`contacts.list`](https://nextmail.sh/docs/ruby/reference/contacts#list), [`contacts.list_all`](https://nextmail.sh/docs/ruby/reference/contacts#listAll), [`contacts.iterate`](https://nextmail.sh/docs/ruby/reference/contacts#iterate); PHP [`contacts->list`](https://nextmail.sh/docs/php/reference/contacts#list), [`contacts->listAll`](https://nextmail.sh/docs/php/reference/contacts#listAll), [`contacts->iterate`](https://nextmail.sh/docs/php/reference/contacts#iterate); Go [`Contacts.List`](https://nextmail.sh/docs/go/reference/contacts#list), [`Contacts.ListAll`](https://nextmail.sh/docs/go/reference/contacts#listAll), [`Contacts.Iterate`](https://nextmail.sh/docs/go/reference/contacts#iterate); Java [`contacts().list`](https://nextmail.sh/docs/java/reference/contacts#list), [`contacts().listAll`](https://nextmail.sh/docs/java/reference/contacts#listAll), [`contacts().iterate`](https://nextmail.sh/docs/java/reference/contacts#iterate); C# [`Contacts.ListAsync`](https://nextmail.sh/docs/csharp/reference/contacts#list), [`Contacts.ListAllAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listAll), [`Contacts.IterateAsync`](https://nextmail.sh/docs/csharp/reference/contacts#iterate); CLI [`nextmail contacts list`](https://nextmail.sh/docs/cli/reference/contacts#contacts-list); MCP [`listContacts`](https://nextmail.sh/docs/mcp/tools/contacts#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.

- Scopes: `contacts:write`.

**Request body**

- `email` (`string`, required, up to 320 characters, format `email`): Trimmed and lower cased before it is stored.
- `name` (`string`, nullable, up to 200 characters): Display name. Leave it out to save the contact without one.
- `notes` (`string`, nullable, up to 5000 characters): Free text kept with the contact and shown beside it in the app.
- `attributes` (`Record<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. `null` or 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 422 `invalid_contact` with `param` set to `attributes.<key>` or `attributes`, and nothing is written.
- `audienceIds` (`string[]`, up to 25 items): 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:write` scope.

**Returns**

- `201` `ContactDetail`: Created.

**Errors**

- `409`: `contact_exists`: that address is already in this workspace book. PATCH it instead.
- `422`: `invalid_contact` when the address is not one or a custom field in `attributes` breaks a rule, with `param` naming the field.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.create()`](https://nextmail.sh/docs/sdk/reference/contacts#create); Python [`contacts.create()`](https://nextmail.sh/docs/python/reference/contacts#create); Ruby [`contacts.create`](https://nextmail.sh/docs/ruby/reference/contacts#create); PHP [`contacts->create`](https://nextmail.sh/docs/php/reference/contacts#create); Go [`Contacts.Create`](https://nextmail.sh/docs/go/reference/contacts#create); Java [`contacts().create`](https://nextmail.sh/docs/java/reference/contacts#create); C# [`Contacts.CreateAsync`](https://nextmail.sh/docs/csharp/reference/contacts#create); CLI [`nextmail contacts create`](https://nextmail.sh/docs/cli/reference/contacts#contacts-create); MCP [`createContact`](https://nextmail.sh/docs/mcp/tools/contacts#createContact), [`saveContact`](https://nextmail.sh/docs/mcp/tools/contacts#saveContact), [`updateContact`](https://nextmail.sh/docs/mcp/tools/contacts#updateContact).

### `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.

- Scopes: `contacts:read`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): An opaque cursor from the previous page, with the same `sort`, `q` and `blocked`. Keyset, not offset: pass the previous page's `nextCursor`. One that names nothing in this list is a 400 `invalid_cursor`.
- `sort` (`string`, one of `"recent"`, `"name"`, `"threads"`, default `"recent"`): `recent` puts the newest mail first, then saved contacts never seen in mail. `name` goes by name, or by address where there is none, ignoring case. `threads` puts the people with the most threads first.
- `q` (`string`, up to 200 characters): Searches names, addresses and notes, with close spellings when nothing matches exactly.
- `email` (`string`, up to 320 characters): One address only, matched case insensitively: the way to read one person's thread count and last mail.
- `blocked` (`string`, one of `"true"`, `"false"`): `true` for only the people the workspace blocklist blocks, whole-domain rules included.

**Returns**

- `200` `PersonList`: A page of people.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.listPeople()`](https://nextmail.sh/docs/sdk/reference/contacts#listPeople), [`nextmail.contacts.listAllPeople()`](https://nextmail.sh/docs/sdk/reference/contacts#listAllPeople), [`nextmail.contacts.iteratePeople()`](https://nextmail.sh/docs/sdk/reference/contacts#iteratePeople); Python [`contacts.list_people()`](https://nextmail.sh/docs/python/reference/contacts#listPeople), [`contacts.list_all_people()`](https://nextmail.sh/docs/python/reference/contacts#listAllPeople), [`contacts.iterate_people()`](https://nextmail.sh/docs/python/reference/contacts#iteratePeople); Ruby [`contacts.list_people`](https://nextmail.sh/docs/ruby/reference/contacts#listPeople), [`contacts.list_all_people`](https://nextmail.sh/docs/ruby/reference/contacts#listAllPeople), [`contacts.iterate_people`](https://nextmail.sh/docs/ruby/reference/contacts#iteratePeople); PHP [`contacts->listPeople`](https://nextmail.sh/docs/php/reference/contacts#listPeople), [`contacts->listAllPeople`](https://nextmail.sh/docs/php/reference/contacts#listAllPeople), [`contacts->iteratePeople`](https://nextmail.sh/docs/php/reference/contacts#iteratePeople); Go [`Contacts.ListPeople`](https://nextmail.sh/docs/go/reference/contacts#listPeople), [`Contacts.ListAllPeople`](https://nextmail.sh/docs/go/reference/contacts#listAllPeople), [`Contacts.IteratePeople`](https://nextmail.sh/docs/go/reference/contacts#iteratePeople); Java [`contacts().listPeople`](https://nextmail.sh/docs/java/reference/contacts#listPeople), [`contacts().listAllPeople`](https://nextmail.sh/docs/java/reference/contacts#listAllPeople), [`contacts().iteratePeople`](https://nextmail.sh/docs/java/reference/contacts#iteratePeople); C# [`Contacts.ListPeopleAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listPeople), [`Contacts.ListAllPeopleAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listAllPeople), [`Contacts.IteratePeopleAsync`](https://nextmail.sh/docs/csharp/reference/contacts#iteratePeople); CLI [`nextmail contacts list-people`](https://nextmail.sh/docs/cli/reference/contacts#contacts-list-people); MCP [`listPeople`](https://nextmail.sh/docs/mcp/tools/contacts#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.

- Scopes: `contacts:write`.

**Request body**

- `emails` (`string[]`, required, 1 to 200 items): Addresses, matched case insensitively. A repeated address counts once.

**Returns**

- `200` `ContactBatchDelete`: What was deleted.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.deleteMany()`](https://nextmail.sh/docs/sdk/reference/contacts#deleteMany); Python [`contacts.delete_many()`](https://nextmail.sh/docs/python/reference/contacts#deleteMany); Ruby [`contacts.delete_many`](https://nextmail.sh/docs/ruby/reference/contacts#deleteMany); PHP [`contacts->deleteMany`](https://nextmail.sh/docs/php/reference/contacts#deleteMany); Go [`Contacts.DeleteMany`](https://nextmail.sh/docs/go/reference/contacts#deleteMany); Java [`contacts().deleteMany`](https://nextmail.sh/docs/java/reference/contacts#deleteMany); C# [`Contacts.DeleteManyAsync`](https://nextmail.sh/docs/csharp/reference/contacts#deleteMany); CLI [`nextmail contacts delete-many`](https://nextmail.sh/docs/cli/reference/contacts#contacts-delete-many); MCP [`deleteContacts`](https://nextmail.sh/docs/mcp/tools/contacts#deleteContacts).

### `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: `grace@example.com` 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.

- Scopes: `contacts:read`.

**Path parameters**

- `email` (`string`, required): The contact's address, matched case insensitively.

**Returns**

- `200` `ContactDetail`: The contact.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.get()`](https://nextmail.sh/docs/sdk/reference/contacts#get); Python [`contacts.get()`](https://nextmail.sh/docs/python/reference/contacts#get); Ruby [`contacts.get`](https://nextmail.sh/docs/ruby/reference/contacts#get); PHP [`contacts->get`](https://nextmail.sh/docs/php/reference/contacts#get); Go [`Contacts.Get`](https://nextmail.sh/docs/go/reference/contacts#get); Java [`contacts().get`](https://nextmail.sh/docs/java/reference/contacts#get); C# [`Contacts.GetAsync`](https://nextmail.sh/docs/csharp/reference/contacts#get); CLI [`nextmail contacts get`](https://nextmail.sh/docs/cli/reference/contacts#contacts-get); MCP [`getContact`](https://nextmail.sh/docs/mcp/tools/contacts#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.

- Scopes: `contacts:write`.

**Path parameters**

- `email` (`string`, required): The contact's address, matched case insensitively.

**Request body**

- `name` (`string`, nullable, up to 200 characters): New display name. Null clears it.
- `notes` (`string`, nullable, up to 5000 characters): New notes. Null clears them.
- `attributes` (`Record<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. `null` or 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 422 `invalid_contact` with `param` set to `attributes.<key>` or `attributes`, and nothing is written.

**Returns**

- `200` `ContactDetail`: Saved.

**Errors**

- `422`: `invalid_contact` when a custom field in `attributes` breaks a rule, with `param` naming the field.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.update()`](https://nextmail.sh/docs/sdk/reference/contacts#update); Python [`contacts.update()`](https://nextmail.sh/docs/python/reference/contacts#update); Ruby [`contacts.update`](https://nextmail.sh/docs/ruby/reference/contacts#update); PHP [`contacts->update`](https://nextmail.sh/docs/php/reference/contacts#update); Go [`Contacts.Update`](https://nextmail.sh/docs/go/reference/contacts#update); Java [`contacts().update`](https://nextmail.sh/docs/java/reference/contacts#update); C# [`Contacts.UpdateAsync`](https://nextmail.sh/docs/csharp/reference/contacts#update); CLI [`nextmail contacts update`](https://nextmail.sh/docs/cli/reference/contacts#contacts-update); MCP [`createContact`](https://nextmail.sh/docs/mcp/tools/contacts#createContact), [`saveContact`](https://nextmail.sh/docs/mcp/tools/contacts#saveContact), [`updateContact`](https://nextmail.sh/docs/mcp/tools/contacts#updateContact).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `email` (`string`, required): The address, trimmed and lower cased on the server.

**Request body**

- `name` (`string`, up to 200 characters): Up to 200 characters. Left out, the stored name is kept.
- `notes` (`string`, nullable, up to 5000 characters): Up to 5,000 characters. `null` clears the notes; left out, they are kept.
- `attributes` (`Record<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. `null` or 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 422 `invalid_contact` with `param` set to `attributes.<key>` or `attributes`, and nothing is written.

**Returns**

- `200` `ContactDetail`: Already a contact, now kept as `manual`.
- `201` `ContactDetail`: Saved as a new contact.

**Errors**

- `422`: `unknown_parameter` for any key but `name`, `notes` and `attributes`, or `invalid_contact` when a custom field breaks a rule.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.save()`](https://nextmail.sh/docs/sdk/reference/contacts#save); Python [`contacts.save()`](https://nextmail.sh/docs/python/reference/contacts#save); Ruby [`contacts.save`](https://nextmail.sh/docs/ruby/reference/contacts#save); PHP [`contacts->save`](https://nextmail.sh/docs/php/reference/contacts#save); Go [`Contacts.Save`](https://nextmail.sh/docs/go/reference/contacts#save); Java [`contacts().save`](https://nextmail.sh/docs/java/reference/contacts#save); C# [`Contacts.SaveAsync`](https://nextmail.sh/docs/csharp/reference/contacts#save); CLI [`nextmail contacts save`](https://nextmail.sh/docs/cli/reference/contacts#contacts-save); MCP [`createContact`](https://nextmail.sh/docs/mcp/tools/contacts#createContact), [`saveContact`](https://nextmail.sh/docs/mcp/tools/contacts#saveContact), [`updateContact`](https://nextmail.sh/docs/mcp/tools/contacts#updateContact).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `email` (`string`, required): The contact's address, matched case insensitively.

**Returns**

- `200` `DeletedContact`: Deleted and hidden.

**Errors**

- `422`: `invalid_contact` when the path is not an address.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.delete()`](https://nextmail.sh/docs/sdk/reference/contacts#delete); Python [`contacts.delete()`](https://nextmail.sh/docs/python/reference/contacts#delete); Ruby [`contacts.delete`](https://nextmail.sh/docs/ruby/reference/contacts#delete); PHP [`contacts->delete`](https://nextmail.sh/docs/php/reference/contacts#delete); Go [`Contacts.Delete`](https://nextmail.sh/docs/go/reference/contacts#delete); Java [`contacts().delete`](https://nextmail.sh/docs/java/reference/contacts#delete); C# [`Contacts.DeleteAsync`](https://nextmail.sh/docs/csharp/reference/contacts#delete); CLI [`nextmail contacts delete`](https://nextmail.sh/docs/cli/reference/contacts#contacts-delete); MCP [`deleteContact`](https://nextmail.sh/docs/mcp/tools/contacts#deleteContact).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `email` (`string`, required): The contact's address, matched case insensitively.

**Request body**

Content type: `image/png`, `image/jpeg`, `image/webp`, `image/gif`.

`binary`

**Returns**

- `200` `ContactDetail`: The contact, with its new `photoUrl`.

**Errors**

- `404`: `contact_not_found` on `email`: the address is not a saved contact.
- `422`: `invalid_image` when 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 return: `400`, `401`, `403`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.setPhoto()`](https://nextmail.sh/docs/sdk/reference/contacts#setPhoto); Python [`contacts.set_photo()`](https://nextmail.sh/docs/python/reference/contacts#setPhoto); Ruby [`contacts.set_photo`](https://nextmail.sh/docs/ruby/reference/contacts#setPhoto); PHP [`contacts->setPhoto`](https://nextmail.sh/docs/php/reference/contacts#setPhoto); Go [`Contacts.SetPhoto`](https://nextmail.sh/docs/go/reference/contacts#setPhoto); Java [`contacts().setPhoto`](https://nextmail.sh/docs/java/reference/contacts#setPhoto); C# [`Contacts.SetPhotoAsync`](https://nextmail.sh/docs/csharp/reference/contacts#setPhoto); CLI [`nextmail contacts set-photo`](https://nextmail.sh/docs/cli/reference/contacts#contacts-set-photo); MCP [`setContactPhoto`](https://nextmail.sh/docs/mcp/tools/contacts#setContactPhoto).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `email` (`string`, required): The contact's address, matched case insensitively.

**Returns**

- `200` `ContactDetail`: The contact, with `photoUrl` null.

**Errors**

- `404`: `contact_not_found` on `email`: the address is not a saved contact.
- The errors every operation can return: `400`, `401`, `403`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.removePhoto()`](https://nextmail.sh/docs/sdk/reference/contacts#removePhoto); Python [`contacts.remove_photo()`](https://nextmail.sh/docs/python/reference/contacts#removePhoto); Ruby [`contacts.remove_photo`](https://nextmail.sh/docs/ruby/reference/contacts#removePhoto); PHP [`contacts->removePhoto`](https://nextmail.sh/docs/php/reference/contacts#removePhoto); Go [`Contacts.RemovePhoto`](https://nextmail.sh/docs/go/reference/contacts#removePhoto); Java [`contacts().removePhoto`](https://nextmail.sh/docs/java/reference/contacts#removePhoto); C# [`Contacts.RemovePhotoAsync`](https://nextmail.sh/docs/csharp/reference/contacts#removePhoto); CLI [`nextmail contacts remove-photo`](https://nextmail.sh/docs/cli/reference/contacts#contacts-remove-photo); MCP [`removeContactPhoto`](https://nextmail.sh/docs/mcp/tools/contacts#removeContactPhoto).

### `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 `ada+news@example.com` blocks `ada@example.com`. 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.

- Scopes: `settings:write`.

**Path parameters**

- `email` (`string`, required): The address to block.

**Returns**

- `200` `ContactBlock`: Blocked.

**Errors**

- `422`: `invalid_contact` when the path is not an address, `blocklist_entry_too_broad` when it has fewer than two letters or numbers, or the narrowed-key refusal. `capability_unsupported` on `addressAllowlist`: 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 return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.block()`](https://nextmail.sh/docs/sdk/reference/contacts#block); Python [`contacts.block()`](https://nextmail.sh/docs/python/reference/contacts#block); Ruby [`contacts.block`](https://nextmail.sh/docs/ruby/reference/contacts#block); PHP [`contacts->block`](https://nextmail.sh/docs/php/reference/contacts#block); Go [`Contacts.Block`](https://nextmail.sh/docs/go/reference/contacts#block); Java [`contacts().block`](https://nextmail.sh/docs/java/reference/contacts#block); C# [`Contacts.BlockAsync`](https://nextmail.sh/docs/csharp/reference/contacts#block); CLI [`nextmail contacts block`](https://nextmail.sh/docs/cli/reference/contacts#contacts-block).

### `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.

- Scopes: `settings:write`.

**Path parameters**

- `email` (`string`, required): The address to unblock.

**Returns**

- `200` `ContactBlock`: Unblocked.

**Errors**

- `422`: `invalid_contact` when the path is not an address, or the narrowed-key refusal. `capability_unsupported` on `addressAllowlist`: 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 return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.unblock()`](https://nextmail.sh/docs/sdk/reference/contacts#unblock); Python [`contacts.unblock()`](https://nextmail.sh/docs/python/reference/contacts#unblock); Ruby [`contacts.unblock`](https://nextmail.sh/docs/ruby/reference/contacts#unblock); PHP [`contacts->unblock`](https://nextmail.sh/docs/php/reference/contacts#unblock); Go [`Contacts.Unblock`](https://nextmail.sh/docs/go/reference/contacts#unblock); Java [`contacts().unblock`](https://nextmail.sh/docs/java/reference/contacts#unblock); C# [`Contacts.UnblockAsync`](https://nextmail.sh/docs/csharp/reference/contacts#unblock); CLI [`nextmail contacts unblock`](https://nextmail.sh/docs/cli/reference/contacts#contacts-unblock).

### `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.

- Scopes: `threads:read`.

**Path parameters**

- `email` (`string`, required): The address. It does not have to be a saved contact.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Threads per page, 1 to 100.
- `cursor` (`string`): The previous page's `nextCursor`, with the same `q` and `sort`.
- `q` (`string`, up to 200 characters): Searches inside those threads, with the same syntax as the mailbox search.
- `sort` (`string`, one of `"newest"`, `"oldest"`, `"sender"`, `"subject"`, default `"newest"`): `newest` (the default), `oldest`, `sender` or `subject`.

**Returns**

- `200` `ContactThreadList`: A page of threads.

**Errors**

- `422`: `invalid_contact` when the path is not an address, or the narrowed-key refusal. `capability_unsupported` on `addressAllowlist`: 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 return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.listThreads()`](https://nextmail.sh/docs/sdk/reference/contacts#listThreads), [`nextmail.contacts.listAllThreads()`](https://nextmail.sh/docs/sdk/reference/contacts#listAllThreads), [`nextmail.contacts.iterateThreads()`](https://nextmail.sh/docs/sdk/reference/contacts#iterateThreads); Python [`contacts.list_threads()`](https://nextmail.sh/docs/python/reference/contacts#listThreads), [`contacts.list_all_threads()`](https://nextmail.sh/docs/python/reference/contacts#listAllThreads), [`contacts.iterate_threads()`](https://nextmail.sh/docs/python/reference/contacts#iterateThreads); Ruby [`contacts.list_threads`](https://nextmail.sh/docs/ruby/reference/contacts#listThreads), [`contacts.list_all_threads`](https://nextmail.sh/docs/ruby/reference/contacts#listAllThreads), [`contacts.iterate_threads`](https://nextmail.sh/docs/ruby/reference/contacts#iterateThreads); PHP [`contacts->listThreads`](https://nextmail.sh/docs/php/reference/contacts#listThreads), [`contacts->listAllThreads`](https://nextmail.sh/docs/php/reference/contacts#listAllThreads), [`contacts->iterateThreads`](https://nextmail.sh/docs/php/reference/contacts#iterateThreads); Go [`Contacts.ListThreads`](https://nextmail.sh/docs/go/reference/contacts#listThreads), [`Contacts.ListAllThreads`](https://nextmail.sh/docs/go/reference/contacts#listAllThreads), [`Contacts.IterateThreads`](https://nextmail.sh/docs/go/reference/contacts#iterateThreads); Java [`contacts().listThreads`](https://nextmail.sh/docs/java/reference/contacts#listThreads), [`contacts().listAllThreads`](https://nextmail.sh/docs/java/reference/contacts#listAllThreads), [`contacts().iterateThreads`](https://nextmail.sh/docs/java/reference/contacts#iterateThreads); C# [`Contacts.ListThreadsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listThreads), [`Contacts.ListAllThreadsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listAllThreads), [`Contacts.IterateThreadsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#iterateThreads); CLI [`nextmail contacts list-threads`](https://nextmail.sh/docs/cli/reference/contacts#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.

- Scopes: `threads:read`.

**Path parameters**

- `email` (`string`, required): The address. It does not have to be a saved contact.

**Query parameters**

- `minutes` (`integer`, at least 1, at most 10540800, default `129600`): How far back the window reaches from now. The default is 90 days.
- `grain` (`string`, one of `"minute"`, `"hour"`, `"day"`, default `"day"`): Bucket width: `minute`, `hour` or `day`, defaulting to `day`.
- `offsetMinutes` (`integer`, at least -840, at most 840, default `0`): The reader's offset from UTC in minutes, so that a day bucket falls on their calendar.

**Returns**

- `200` `ContactActivity`: The activity.

**Errors**

- `422`: `invalid_contact` when the path is not an address, or the narrowed-key refusal. `capability_unsupported` on `addressAllowlist`: 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 return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.activity()`](https://nextmail.sh/docs/sdk/reference/contacts#activity); Python [`contacts.activity()`](https://nextmail.sh/docs/python/reference/contacts#activity); Ruby [`contacts.activity`](https://nextmail.sh/docs/ruby/reference/contacts#activity); PHP [`contacts->activity`](https://nextmail.sh/docs/php/reference/contacts#activity); Go [`Contacts.Activity`](https://nextmail.sh/docs/go/reference/contacts#activity); Java [`contacts().activity`](https://nextmail.sh/docs/java/reference/contacts#activity); C# [`Contacts.ActivityAsync`](https://nextmail.sh/docs/csharp/reference/contacts#activity); CLI [`nextmail contacts activity`](https://nextmail.sh/docs/cli/reference/contacts#contacts-activity); MCP [`getContactActivity`](https://nextmail.sh/docs/mcp/tools/contacts#getContactActivity).

### `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.

- Scopes: `audiences:write`.

**Path parameters**

- `email` (`string`, required): The contact's address, matched case insensitively.

**Request body**

- `audienceIds` (`string[]`, required, up to 100 items): Every 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.

**Returns**

- `200` `ContactDetail`: The contact, with the audiences it is in after the change.

**Errors**

- `404`: `contact_not_found` on `email` when the address is not in the workspace book, or `audience_not_found` on `audienceIds` when an id names no audience here. Nothing is changed.
- `422`: `invalid_parameter` on `audienceIds` for more than 100 ids, and on the entry, such as `audienceIds.0`, for an empty one, `unknown_parameter` for any other key in the body.
- The errors every operation can return: `400`, `401`, `403`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.setAudiences()`](https://nextmail.sh/docs/sdk/reference/contacts#setAudiences); Python [`contacts.set_audiences()`](https://nextmail.sh/docs/python/reference/contacts#setAudiences); Ruby [`contacts.set_audiences`](https://nextmail.sh/docs/ruby/reference/contacts#setAudiences); PHP [`contacts->setAudiences`](https://nextmail.sh/docs/php/reference/contacts#setAudiences); Go [`Contacts.SetAudiences`](https://nextmail.sh/docs/go/reference/contacts#setAudiences); Java [`contacts().setAudiences`](https://nextmail.sh/docs/java/reference/contacts#setAudiences); C# [`Contacts.SetAudiencesAsync`](https://nextmail.sh/docs/csharp/reference/contacts#setAudiences); CLI [`nextmail contacts set-audiences`](https://nextmail.sh/docs/cli/reference/contacts#contacts-set-audiences); MCP [`setContactAudiences`](https://nextmail.sh/docs/mcp/tools/audiences#setContactAudiences).

### `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.

- Scopes: `contacts:read`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 200): Cards per page, 1 to 200. 50 when left out.
- `cursor` (`string`, 1 to 64 characters): The `nextCursor` from the previous page. One that names no card is a 400 `invalid_cursor`.
- `email` (`string`, 1 to 320 characters): Only the card of the saved contact with this address.
- `withoutEmail` (`string`, one of `"true"`, `"false"`): `true` lists only the cards that have no email address.
- `kind` (`string`, one of `"individual"`, `"group"`): `group` lists only the groups, `individual` only the people and companies.
- `groupId` (`string`, 1 to 64 characters): Only the cards in the group with this id.

**Returns**

- `200` `ContactCardList`: One page of cards.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.listCards()`](https://nextmail.sh/docs/sdk/reference/contacts#listCards), [`nextmail.contacts.listAllCards()`](https://nextmail.sh/docs/sdk/reference/contacts#listAllCards), [`nextmail.contacts.iterateCards()`](https://nextmail.sh/docs/sdk/reference/contacts#iterateCards); Python [`contacts.list_cards()`](https://nextmail.sh/docs/python/reference/contacts#listCards), [`contacts.list_all_cards()`](https://nextmail.sh/docs/python/reference/contacts#listAllCards), [`contacts.iterate_cards()`](https://nextmail.sh/docs/python/reference/contacts#iterateCards); Ruby [`contacts.list_cards`](https://nextmail.sh/docs/ruby/reference/contacts#listCards), [`contacts.list_all_cards`](https://nextmail.sh/docs/ruby/reference/contacts#listAllCards), [`contacts.iterate_cards`](https://nextmail.sh/docs/ruby/reference/contacts#iterateCards); PHP [`contacts->listCards`](https://nextmail.sh/docs/php/reference/contacts#listCards), [`contacts->listAllCards`](https://nextmail.sh/docs/php/reference/contacts#listAllCards), [`contacts->iterateCards`](https://nextmail.sh/docs/php/reference/contacts#iterateCards); Go [`Contacts.ListCards`](https://nextmail.sh/docs/go/reference/contacts#listCards), [`Contacts.ListAllCards`](https://nextmail.sh/docs/go/reference/contacts#listAllCards), [`Contacts.IterateCards`](https://nextmail.sh/docs/go/reference/contacts#iterateCards); Java [`contacts().listCards`](https://nextmail.sh/docs/java/reference/contacts#listCards), [`contacts().listAllCards`](https://nextmail.sh/docs/java/reference/contacts#listAllCards), [`contacts().iterateCards`](https://nextmail.sh/docs/java/reference/contacts#iterateCards); C# [`Contacts.ListCardsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listCards), [`Contacts.ListAllCardsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listAllCards), [`Contacts.IterateCardsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#iterateCards); CLI [`nextmail contacts list-cards`](https://nextmail.sh/docs/cli/reference/contacts#contacts-list-cards); MCP [`getContactCard`](https://nextmail.sh/docs/mcp/tools/contacts#getContactCard), [`listContactCards`](https://nextmail.sh/docs/mcp/tools/contacts#listContactCards).

### `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.

- Scopes: `contacts:write`.

**Request body**

- `email` (`string`, nullable, up to 320 characters, format `email`): The 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.
- `name` (`string`, nullable, up to 200 characters): The full name the address book shows.
- `nameParts` (`object`, nullable): The name in parts, the way phones keep it. Left out, phones split name themselves.
  - `prefix` (`string`, nullable, up to 200 characters, default `null`): A title before the name, such as Dr.
  - `given` (`string`, nullable, up to 200 characters, default `null`): The first name.
  - `middle` (`string`, nullable, up to 200 characters, default `null`): Any middle names.
  - `family` (`string`, nullable, up to 200 characters, default `null`): The surname.
  - `suffix` (`string`, nullable, up to 200 characters, default `null`): Anything after the name, such as Jr.
- `nickname` (`string`, nullable, up to 200 characters): A nickname.
- `organization` (`string`, nullable, up to 200 characters): The company or organisation.
- `department` (`string`, nullable, up to 200 characters): The department within the organisation.
- `title` (`string`, nullable, up to 200 characters): The job title.
- `birthday` (`string`, nullable, pattern `^(?:\d{4}-|--)(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$`): YYYY-MM-DD, or --MM-DD when the year is not known.
- `notes` (`string`, nullable, up to 5000 characters): Free-form notes. On a saved contact these are the notes the contact page shows.
- `emails` (`object[]`, up to 50 items): Other email addresses, besides email. Given, it replaces the whole list.
  - `value` (`string`, required, up to 320 characters, format `email`): The email address.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
- `phones` (`object[]`, up to 50 items): Phone numbers. Given, it replaces the whole list.
  - `value` (`string`, required, 1 to 320 characters): The number or the link.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
- `addresses` (`object[]`, up to 50 items): Postal addresses. Given, it replaces the whole list.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
  - `street` (`string`, nullable, up to 200 characters, default `null`): The street and house number, on as many lines as it needs.
  - `locality` (`string`, nullable, up to 200 characters, default `null`): The city or town.
  - `region` (`string`, nullable, up to 200 characters, default `null`): The state, county or province.
  - `postalCode` (`string`, nullable, up to 200 characters, default `null`): The postal code.
  - `country` (`string`, nullable, up to 200 characters, default `null`): The country.
- `urls` (`object[]`, up to 50 items): Websites and profile links. Given, it replaces the whole list.
  - `value` (`string`, required, 1 to 320 characters): The number or the link.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
- `kind` (`string`, one of `"individual"`, `"group"`): `individual` for a person or a company, `group` for a list of other cards, the groups phones keep.
- `members` (`string[]`, up to 2000 items): The ids of the cards in the group. Only a group takes it, and a list you send replaces the whole list.

**Returns**

- `201` `ContactCard`: 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 return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.createCard()`](https://nextmail.sh/docs/sdk/reference/contacts#createCard); Python [`contacts.create_card()`](https://nextmail.sh/docs/python/reference/contacts#createCard); Ruby [`contacts.create_card`](https://nextmail.sh/docs/ruby/reference/contacts#createCard); PHP [`contacts->createCard`](https://nextmail.sh/docs/php/reference/contacts#createCard); Go [`Contacts.CreateCard`](https://nextmail.sh/docs/go/reference/contacts#createCard); Java [`contacts().createCard`](https://nextmail.sh/docs/java/reference/contacts#createCard); C# [`Contacts.CreateCardAsync`](https://nextmail.sh/docs/csharp/reference/contacts#createCard); CLI [`nextmail contacts create-card`](https://nextmail.sh/docs/cli/reference/contacts#contacts-create-card); MCP [`createContactCard`](https://nextmail.sh/docs/mcp/tools/contacts#createContactCard).

### `GET /contacts/cards/{id}`

Get a contact card

One card with everything it holds.

Requires the `contacts:read` scope.

- Scopes: `contacts:read`.

**Path parameters**

- `id` (`string`, required): The card id from `GET /contacts/cards`.

**Returns**

- `200` `ContactCard`: The card.

**Errors**

- `404`: `contact_card_not_found`: no card has that id.
- The errors every operation can return: `400`, `401`, `403`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.getCard()`](https://nextmail.sh/docs/sdk/reference/contacts#getCard); Python [`contacts.get_card()`](https://nextmail.sh/docs/python/reference/contacts#getCard); Ruby [`contacts.get_card`](https://nextmail.sh/docs/ruby/reference/contacts#getCard); PHP [`contacts->getCard`](https://nextmail.sh/docs/php/reference/contacts#getCard); Go [`Contacts.GetCard`](https://nextmail.sh/docs/go/reference/contacts#getCard); Java [`contacts().getCard`](https://nextmail.sh/docs/java/reference/contacts#getCard); C# [`Contacts.GetCardAsync`](https://nextmail.sh/docs/csharp/reference/contacts#getCard); CLI [`nextmail contacts get-card`](https://nextmail.sh/docs/cli/reference/contacts#contacts-get-card); MCP [`getContactCard`](https://nextmail.sh/docs/mcp/tools/contacts#getContactCard).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `id` (`string`, required): The card id from `GET /contacts/cards`.

**Request body**

- `email` (`string`, nullable, up to 320 characters, format `email`): The 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.
- `name` (`string`, nullable, up to 200 characters): The full name the address book shows.
- `nameParts` (`object`, nullable): The name in parts, the way phones keep it. Left out, phones split name themselves.
  - `prefix` (`string`, nullable, up to 200 characters, default `null`): A title before the name, such as Dr.
  - `given` (`string`, nullable, up to 200 characters, default `null`): The first name.
  - `middle` (`string`, nullable, up to 200 characters, default `null`): Any middle names.
  - `family` (`string`, nullable, up to 200 characters, default `null`): The surname.
  - `suffix` (`string`, nullable, up to 200 characters, default `null`): Anything after the name, such as Jr.
- `nickname` (`string`, nullable, up to 200 characters): A nickname.
- `organization` (`string`, nullable, up to 200 characters): The company or organisation.
- `department` (`string`, nullable, up to 200 characters): The department within the organisation.
- `title` (`string`, nullable, up to 200 characters): The job title.
- `birthday` (`string`, nullable, pattern `^(?:\d{4}-|--)(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$`): YYYY-MM-DD, or --MM-DD when the year is not known.
- `notes` (`string`, nullable, up to 5000 characters): Free-form notes. On a saved contact these are the notes the contact page shows.
- `emails` (`object[]`, up to 50 items): Other email addresses, besides email. Given, it replaces the whole list.
  - `value` (`string`, required, up to 320 characters, format `email`): The email address.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
- `phones` (`object[]`, up to 50 items): Phone numbers. Given, it replaces the whole list.
  - `value` (`string`, required, 1 to 320 characters): The number or the link.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
- `addresses` (`object[]`, up to 50 items): Postal addresses. Given, it replaces the whole list.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
  - `street` (`string`, nullable, up to 200 characters, default `null`): The street and house number, on as many lines as it needs.
  - `locality` (`string`, nullable, up to 200 characters, default `null`): The city or town.
  - `region` (`string`, nullable, up to 200 characters, default `null`): The state, county or province.
  - `postalCode` (`string`, nullable, up to 200 characters, default `null`): The postal code.
  - `country` (`string`, nullable, up to 200 characters, default `null`): The country.
- `urls` (`object[]`, up to 50 items): Websites and profile links. Given, it replaces the whole list.
  - `value` (`string`, required, 1 to 320 characters): The number or the link.
  - `label` (`string`, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`, default `"other"`): What the entry is for: home, work, mobile, main, fax, pager or other.
  - `customLabel` (`string`, nullable, up to 60 characters, default `null`): A label of your own, such as Assistant. Address books that support it show it instead of label.
- `kind` (`string`, one of `"individual"`, `"group"`): `individual` for a person or a company, `group` for a list of other cards, the groups phones keep.
- `members` (`string[]`, up to 2000 items): The ids of the cards in the group. Only a group takes it, and a list you send replaces the whole list.

**Returns**

- `200` `ContactCard`: 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 return: `400`, `401`, `403`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.updateCard()`](https://nextmail.sh/docs/sdk/reference/contacts#updateCard); Python [`contacts.update_card()`](https://nextmail.sh/docs/python/reference/contacts#updateCard); Ruby [`contacts.update_card`](https://nextmail.sh/docs/ruby/reference/contacts#updateCard); PHP [`contacts->updateCard`](https://nextmail.sh/docs/php/reference/contacts#updateCard); Go [`Contacts.UpdateCard`](https://nextmail.sh/docs/go/reference/contacts#updateCard); Java [`contacts().updateCard`](https://nextmail.sh/docs/java/reference/contacts#updateCard); C# [`Contacts.UpdateCardAsync`](https://nextmail.sh/docs/csharp/reference/contacts#updateCard); CLI [`nextmail contacts update-card`](https://nextmail.sh/docs/cli/reference/contacts#contacts-update-card); MCP [`updateContactCard`](https://nextmail.sh/docs/mcp/tools/contacts#updateContactCard).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `id` (`string`, required): The card id from `GET /contacts/cards`.

**Returns**

- `200` `DeletedContactCard`: The card is gone.

**Errors**

- `404`: `contact_card_not_found`: no card has that id.
- The errors every operation can return: `400`, `401`, `403`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.deleteCard()`](https://nextmail.sh/docs/sdk/reference/contacts#deleteCard); Python [`contacts.delete_card()`](https://nextmail.sh/docs/python/reference/contacts#deleteCard); Ruby [`contacts.delete_card`](https://nextmail.sh/docs/ruby/reference/contacts#deleteCard); PHP [`contacts->deleteCard`](https://nextmail.sh/docs/php/reference/contacts#deleteCard); Go [`Contacts.DeleteCard`](https://nextmail.sh/docs/go/reference/contacts#deleteCard); Java [`contacts().deleteCard`](https://nextmail.sh/docs/java/reference/contacts#deleteCard); C# [`Contacts.DeleteCardAsync`](https://nextmail.sh/docs/csharp/reference/contacts#deleteCard); CLI [`nextmail contacts delete-card`](https://nextmail.sh/docs/cli/reference/contacts#contacts-delete-card); MCP [`deleteContactCard`](https://nextmail.sh/docs/mcp/tools/contacts#deleteContactCard).

### `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.

- Scopes: `contacts:read`.

**Returns**

- `200` `ContactCardDuplicateList`: The groups of cards that look like one person.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.findDuplicateCards()`](https://nextmail.sh/docs/sdk/reference/contacts#findDuplicateCards); Python [`contacts.find_duplicate_cards()`](https://nextmail.sh/docs/python/reference/contacts#findDuplicateCards); Ruby [`contacts.find_duplicate_cards`](https://nextmail.sh/docs/ruby/reference/contacts#findDuplicateCards); PHP [`contacts->findDuplicateCards`](https://nextmail.sh/docs/php/reference/contacts#findDuplicateCards); Go [`Contacts.FindDuplicateCards`](https://nextmail.sh/docs/go/reference/contacts#findDuplicateCards); Java [`contacts().findDuplicateCards`](https://nextmail.sh/docs/java/reference/contacts#findDuplicateCards); C# [`Contacts.FindDuplicateCardsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#findDuplicateCards); CLI [`nextmail contacts find-duplicate-cards`](https://nextmail.sh/docs/cli/reference/contacts#contacts-find-duplicate-cards); MCP [`findDuplicateContacts`](https://nextmail.sh/docs/mcp/tools/contacts#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.

- Scopes: `contacts:write`.

**Path parameters**

- `id` (`string`, required): The card id from `GET /contacts/cards`.

**Request body**

- `cardIds` (`string[]`, required, 1 to 10 items): The cards to fold into this one. They are deleted once their details are kept.

**Returns**

- `200` `ContactCard`: 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 return: `400`, `401`, `403`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.mergeCards()`](https://nextmail.sh/docs/sdk/reference/contacts#mergeCards); Python [`contacts.merge_cards()`](https://nextmail.sh/docs/python/reference/contacts#mergeCards); Ruby [`contacts.merge_cards`](https://nextmail.sh/docs/ruby/reference/contacts#mergeCards); PHP [`contacts->mergeCards`](https://nextmail.sh/docs/php/reference/contacts#mergeCards); Go [`Contacts.MergeCards`](https://nextmail.sh/docs/go/reference/contacts#mergeCards); Java [`contacts().mergeCards`](https://nextmail.sh/docs/java/reference/contacts#mergeCards); C# [`Contacts.MergeCardsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#mergeCards); CLI [`nextmail contacts merge-cards`](https://nextmail.sh/docs/cli/reference/contacts#contacts-merge-cards); MCP [`mergeContactCards`](https://nextmail.sh/docs/mcp/tools/contacts#mergeContactCards).

### `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.

- Scopes: `contacts:read`.

**Query parameters**

- `from` (`string`): Start of the window.
- `to` (`string`): End of the window, after `from` and at most 366 days later.
- `timezone` (`string`, 1 to 64 characters, default `"UTC"`): IANA zone such as `Europe/London`. Defaults to `UTC`.

**Returns**

- `200` `ContactBirthdayList`: Every birthday in the window, sorted by date and then by name.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.listBirthdays()`](https://nextmail.sh/docs/sdk/reference/contacts#listBirthdays); Python [`contacts.list_birthdays()`](https://nextmail.sh/docs/python/reference/contacts#listBirthdays); Ruby [`contacts.list_birthdays`](https://nextmail.sh/docs/ruby/reference/contacts#listBirthdays); PHP [`contacts->listBirthdays`](https://nextmail.sh/docs/php/reference/contacts#listBirthdays); Go [`Contacts.ListBirthdays`](https://nextmail.sh/docs/go/reference/contacts#listBirthdays); Java [`contacts().listBirthdays`](https://nextmail.sh/docs/java/reference/contacts#listBirthdays); C# [`Contacts.ListBirthdaysAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listBirthdays); CLI [`nextmail contacts list-birthdays`](https://nextmail.sh/docs/cli/reference/contacts#contacts-list-birthdays); MCP [`listBirthdays`](https://nextmail.sh/docs/mcp/tools/contacts#listBirthdays).

### `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.

- Scopes: `contacts:read`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): The 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 400 `invalid_cursor`.

**Returns**

- `200` `ContactImportList`: One page of contacts imports.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.listImports()`](https://nextmail.sh/docs/sdk/reference/contacts#listImports), [`nextmail.contacts.listAllImports()`](https://nextmail.sh/docs/sdk/reference/contacts#listAllImports), [`nextmail.contacts.iterateImports()`](https://nextmail.sh/docs/sdk/reference/contacts#iterateImports); Python [`contacts.list_imports()`](https://nextmail.sh/docs/python/reference/contacts#listImports), [`contacts.list_all_imports()`](https://nextmail.sh/docs/python/reference/contacts#listAllImports), [`contacts.iterate_imports()`](https://nextmail.sh/docs/python/reference/contacts#iterateImports); Ruby [`contacts.list_imports`](https://nextmail.sh/docs/ruby/reference/contacts#listImports), [`contacts.list_all_imports`](https://nextmail.sh/docs/ruby/reference/contacts#listAllImports), [`contacts.iterate_imports`](https://nextmail.sh/docs/ruby/reference/contacts#iterateImports); PHP [`contacts->listImports`](https://nextmail.sh/docs/php/reference/contacts#listImports), [`contacts->listAllImports`](https://nextmail.sh/docs/php/reference/contacts#listAllImports), [`contacts->iterateImports`](https://nextmail.sh/docs/php/reference/contacts#iterateImports); Go [`Contacts.ListImports`](https://nextmail.sh/docs/go/reference/contacts#listImports), [`Contacts.ListAllImports`](https://nextmail.sh/docs/go/reference/contacts#listAllImports), [`Contacts.IterateImports`](https://nextmail.sh/docs/go/reference/contacts#iterateImports); Java [`contacts().listImports`](https://nextmail.sh/docs/java/reference/contacts#listImports), [`contacts().listAllImports`](https://nextmail.sh/docs/java/reference/contacts#listAllImports), [`contacts().iterateImports`](https://nextmail.sh/docs/java/reference/contacts#iterateImports); C# [`Contacts.ListImportsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listImports), [`Contacts.ListAllImportsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#listAllImports), [`Contacts.IterateImportsAsync`](https://nextmail.sh/docs/csharp/reference/contacts#iterateImports); CLI [`nextmail contacts list-imports`](https://nextmail.sh/docs/cli/reference/contacts#contacts-list-imports); MCP [`listContactImports`](https://nextmail.sh/docs/mcp/tools/contacts#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.

- Scopes: `contacts:write`.

**Query parameters**

- `filename` (`string`, up to 255 characters): The name to show for the file in the list of imports.

**Request body**

Content type: `text/vcard`, `application/octet-stream`.

`binary`

**Returns**

- `201` `ContactImport`: 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 return: `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.importVcf()`](https://nextmail.sh/docs/sdk/reference/contacts#importVcf); Python [`contacts.import_vcf()`](https://nextmail.sh/docs/python/reference/contacts#importVcf); Ruby [`contacts.import_vcf`](https://nextmail.sh/docs/ruby/reference/contacts#importVcf); PHP [`contacts->importVcf`](https://nextmail.sh/docs/php/reference/contacts#importVcf); Go [`Contacts.ImportVcf`](https://nextmail.sh/docs/go/reference/contacts#importVcf); Java [`contacts().importVcf`](https://nextmail.sh/docs/java/reference/contacts#importVcf); C# [`Contacts.ImportVcfAsync`](https://nextmail.sh/docs/csharp/reference/contacts#importVcf); CLI [`nextmail contacts import-vcf`](https://nextmail.sh/docs/cli/reference/contacts#contacts-import-vcf).

### `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.

- Scopes: `contacts:read`.

**Path parameters**

- `id` (`string`, required): Contacts import id, `iimp_` followed by 24 hex characters.

**Returns**

- `200` `ContactImport`: The import.

**Errors**

- `404`: `item_import_not_found`: no such import.
- The errors every operation can return: `400`, `401`, `403`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.getImport()`](https://nextmail.sh/docs/sdk/reference/contacts#getImport); Python [`contacts.get_import()`](https://nextmail.sh/docs/python/reference/contacts#getImport); Ruby [`contacts.get_import`](https://nextmail.sh/docs/ruby/reference/contacts#getImport); PHP [`contacts->getImport`](https://nextmail.sh/docs/php/reference/contacts#getImport); Go [`Contacts.GetImport`](https://nextmail.sh/docs/go/reference/contacts#getImport); Java [`contacts().getImport`](https://nextmail.sh/docs/java/reference/contacts#getImport); C# [`Contacts.GetImportAsync`](https://nextmail.sh/docs/csharp/reference/contacts#getImport); CLI [`nextmail contacts get-import`](https://nextmail.sh/docs/cli/reference/contacts#contacts-get-import); MCP [`getContactImport`](https://nextmail.sh/docs/mcp/tools/contacts#getContactImport).

### `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.

- Scopes: `contacts:write`.

**Path parameters**

- `id` (`string`, required): Contacts import id, `iimp_` followed by 24 hex characters.

**Returns**

- `200` `ContactImport`: 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 return: `400`, `401`, `403`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.contacts.undoImport()`](https://nextmail.sh/docs/sdk/reference/contacts#undoImport); Python [`contacts.undo_import()`](https://nextmail.sh/docs/python/reference/contacts#undoImport); Ruby [`contacts.undo_import`](https://nextmail.sh/docs/ruby/reference/contacts#undoImport); PHP [`contacts->undoImport`](https://nextmail.sh/docs/php/reference/contacts#undoImport); Go [`Contacts.UndoImport`](https://nextmail.sh/docs/go/reference/contacts#undoImport); Java [`contacts().undoImport`](https://nextmail.sh/docs/java/reference/contacts#undoImport); C# [`Contacts.UndoImportAsync`](https://nextmail.sh/docs/csharp/reference/contacts#undoImport); CLI [`nextmail contacts undo-import`](https://nextmail.sh/docs/cli/reference/contacts#contacts-undo-import); MCP [`undoContactImport`](https://nextmail.sh/docs/mcp/tools/contacts#undoContactImport).

### Objects

#### `Contact`

`object`

- `object` (`string`, one of `"contact"`)
- `email` (`string`): Trimmed and lower cased on write. This is the key every contact route takes, because no contact id is exposed.
- `name` (`string`, nullable)
- `source` (`string`, one of `"manual"`, `"auto"`, `"form"`): `auto` when a send from the app composer recorded the address, `manual` when somebody saved it here or in the app. A recorded send never downgrades a `manual` contact to `auto`.
- `notes` (`string`, nullable)
- `attributes` (`Record<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 its `update_field` step writes one.
- `photoUrl` (`string`, nullable): Where 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.
- `lastSeenAt` (`string`, nullable, format `date-time`): When mail last went to this address from the app composer. Null until it does.
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`)

#### `ContactActivity`

`object`

- `object` (`string`, one of `"contact_activity"`)
- `email` (`string`)
- `since` (`string`, format `date-time`): The start of the first bucket, on the boundary `grain` and `offsetMinutes` put it.
- `until` (`string`, format `date-time`)
- `grain` (`string`, one of `"minute"`, `"hour"`, `"day"`)
- `buckets` (`object[]`): Only the buckets with mail in them, oldest first.
  - `bucket` (`string`): The bucket in the reader's time: `2026-09-23`, `2026-09-23T14` or `2026-09-23T14:05`.
  - `received` (`integer`): Messages from this address.
  - `sent` (`integer`): Messages from the mailbox to this address.
- `totals` (`object`)
  - `received` (`integer`)
  - `sent` (`integer`)
  - `threads` (`integer`): Threads with at least one message in the window.
  - `waiting` (`integer`): Threads whose newest message is from this address, so the mailbox owes the reply.
  - `lastReceivedAt` (`string`, nullable, format `date-time`)
  - `lastSentAt` (`string`, nullable, format `date-time`)
- `replyTime` (`object`)
  - `yours` (`number`, nullable): Median milliseconds the mailbox took to answer, or null without a reply to measure.
  - `theirs` (`number`, nullable): Median milliseconds this address took to answer.

#### `ContactBatchDelete`

`object`

- `object` (`string`, one of `"contact_batch_delete"`)
- `deleted` (`integer`): Addresses deleted and hidden, saved or not.
- `saved` (`integer`): Of those, how many were saved contacts.
- `invalid` (`string[]`): Entries that are not addresses, lower cased. Nothing happened to them.

#### `ContactBirthday`

`object`

- `object` (`string`, required, one of `"contact_birthday"`)
- `cardId` (`string`, required): The card the birthday is on.
- `contactId` (`string`, required, nullable): The saved contact the card belongs to, or null for a card with no address.
- `email` (`string`, required, nullable): The address of that saved contact.
- `name` (`string`, required, nullable): The name on the card, or its organisation.
- `birthday` (`string`, required): The birthday as the card holds it: YYYY-MM-DD, or --MM-DD when the year is not known.
- `date` (`string`, required, format `date`): The day in the window the birthday falls on.
- `age` (`integer`, required, nullable): How old they turn that day, or null when the year of birth is not known.
- `photoUrl` (`string`, required, nullable): Where the contact photo is served, or null when there is none.

#### `ContactBirthdayList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`ContactBirthday[]`)

#### `ContactBlock`

`object`

- `object` (`string`, one of `"contact_block"`)
- `email` (`string`): The address as the blocklist sees it: lower cased, with any plus tag dropped.
- `blocked` (`boolean`)
- `blockedBy` (`object`, nullable): On a block, the rule that now blocks the address.
  - `rule` (`string`): The entry on the workspace blocklist that matches this address, exactly as it is stored.
  - `list` (`string`, one of `"blockedSenders"`, `"blockedDomains"`): `blockedSenders` for an address or pattern rule, `blockedDomains` for a rule that blocks a whole domain and every subdomain under it.
- `created` (`boolean`): On a block, false when a rule already blocked the address and nothing was added.
- `removed` (`object[]`): On an unblock, every rule taken off the workspace blocklist. A `blockedDomains` entry here unblocked the whole domain.
  - `rule` (`string`): The entry on the workspace blocklist that matches this address, exactly as it is stored.
  - `list` (`string`, one of `"blockedSenders"`, `"blockedDomains"`): `blockedSenders` for an address or pattern rule, `blockedDomains` for a rule that blocks a whole domain and every subdomain under it.

#### `ContactCard`

`object`

- `object` (`string`, required, one of `"contact_card"`)
- `id` (`string`, required)
- `uid` (`string`, required): The vCard UID that phones and computers know the card by.
- `kind` (`string`, required, one of `"individual"`, `"group"`): `individual` for a person or a company, `group` for a list of other cards, the groups phones keep.
- `members` (`string[]`, required): For a group, the ids of the cards in it. Empty for everyone else.
- `email` (`string`, required, nullable): The address of the saved contact this card belongs to, the one `/contacts/{email}` takes. null for a card with no address.
- `name` (`string`, required, nullable): The full name.
- `nameParts` (`object`, required, nullable): The name in parts, when somebody set them or a phone sent them.
  - `prefix` (`string`, required, nullable): A title before the name, such as Dr.
  - `given` (`string`, required, nullable): The first name.
  - `middle` (`string`, required, nullable): Any middle names.
  - `family` (`string`, required, nullable): The surname.
  - `suffix` (`string`, required, nullable): Anything after the name, such as Jr.
- `nickname` (`string`, required, nullable): A nickname.
- `organization` (`string`, required, nullable): The company or organisation.
- `department` (`string`, required, nullable): The department within the organisation.
- `title` (`string`, required, nullable): The job title.
- `birthday` (`string`, required, nullable): YYYY-MM-DD, or --MM-DD when the year is not known.
- `notes` (`string`, required, nullable): Free-form notes.
- `emails` (`ContactCardValue[]`, required): Other email addresses, besides email.
- `phones` (`ContactCardValue[]`, required)
- `addresses` (`ContactCardAddress[]`, required)
- `urls` (`ContactCardValue[]`, required): Websites and profile links.
- `photoUrl` (`string`, required, nullable): Where the contact photo is served, or null when there is none.
- `createdAt` (`string`, required, format `date-time`)
- `updatedAt` (`string`, required, format `date-time`)

#### `ContactCardAddress`

`object`

- `label` (`string`, required, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`): What the entry is for.
- `customLabel` (`string`, required, nullable): A label of your own, such as Assistant, shown instead of label where the address book supports it.
- `street` (`string`, required, nullable): The street and house number.
- `locality` (`string`, required, nullable): The city or town.
- `region` (`string`, required, nullable): The state, county or province.
- `postalCode` (`string`, required, nullable): The postal code.
- `country` (`string`, required, nullable): The country.

#### `ContactCardDuplicateGroup`

`object`

- `object` (`string`, required, one of `"contact_card_duplicates"`)
- `reason` (`string`, required, one of `"email"`, `"phone"`, `"name"`): The strongest thing the cards share: an `email` address, a `phone` number or the full `name`.
- `cards` (`ContactCard[]`, required): The cards, the most complete one first.

#### `ContactCardDuplicateList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`ContactCardDuplicateGroup[]`)
- `hasMore` (`boolean`): True when there are more than 100 groups. Merge these and ask again for the rest.

#### `ContactCardList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`ContactCard[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): The id of the last row on this page, or null on the last page.

#### `ContactCardValue`

`object`

- `value` (`string`, required): The address, number or link.
- `label` (`string`, required, one of `"home"`, `"work"`, `"mobile"`, `"main"`, `"fax"`, `"pager"`, `"other"`): What the entry is for.
- `customLabel` (`string`, required, nullable): A label of your own, such as Assistant, shown instead of label where the address book supports it.

#### `ContactDetail`

`object`

- `object` (`string`, one of `"contact"`)
- `email` (`string`): Trimmed and lower cased on write. This is the key every contact route takes, because no contact id is exposed.
- `name` (`string`, nullable)
- `source` (`string`, one of `"manual"`, `"auto"`, `"form"`): `auto` when a send from the app composer recorded the address, `manual` when somebody saved it here or in the app. A recorded send never downgrades a `manual` contact to `auto`.
- `notes` (`string`, nullable)
- `attributes` (`Record<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 its `update_field` step writes one.
- `photoUrl` (`string`, nullable): Where 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.
- `lastSeenAt` (`string`, nullable, format `date-time`): When mail last went to this address from the app composer. Null until it does.
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`)
- `audiences` (`object[]`): Every audience this contact is in, the built-in default one included. Single-contact responses carry it; the list route does not.
  - `id` (`string`): The audience handle, `aud_` plus 24 hex.
  - `name` (`string`)
  - `builtin` (`string`, nullable, one of `"default"`): `default` on the one audience every contact joins, null on an audience somebody created.
- `signUps` (`ContactSignUp[]`): 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.

#### `ContactImport`

`object`

- `object` (`string`, required, one of `"contact_import"`)
- `id` (`string`, required): `iimp_` followed by 24 hex characters.
- `kind` (`string`, required, one of `"contacts"`)
- `source` (`string`, required, one of `"file"`, `"takeout"`, `"caldav"`, `"carddav"`, `"microsoft"`): `file` is an upload. The others came with a mailbox import: `takeout` from a Google Takeout archive, `caldav` and `carddav` from the old provider, `microsoft` from an Outlook sign-in.
- `status` (`string`, required, one of `"uploading"`, `"queued"`, `"running"`, `"completed"`, `"failed"`, `"cancelled"`, `"undone"`): `undone` means what it brought in was removed again.
- `fileName` (`string`, required, nullable)
- `fileBytes` (`integer`, required, at least 0)
- `parentImportId` (`string`, required, nullable): The mailbox import it came with, when there was one.
- `counts` (`object`, required)
  - `total` (`integer`, required, at least 0): Items found so far.
  - `imported` (`integer`, required, at least 0): Items added.
  - `merged` (`integer`, required, at least 0): Contacts 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`.
  - `skipped` (`integer`, required, at least 0): Items left out for a reason given in `report`.
  - `failed` (`integer`, required, at least 0)
- `report` (`object`, required)
  - `collections` (`object[]`, required): The calendars or address books the items came from, by their original name.
    - `name` (`string`, required)
    - `items` (`integer`, required, at least 0)
  - `skipped` (`Record<string, integer>`, required): How many items were left out, by reason.
  - `samples` (`object[]`, required): Up to 20 of the items left out.
    - `name` (`string`, required)
    - `reason` (`string`, required, one of `"uid-taken"`, `"no-start"`, `"duplicate"`, `"limit"`, `"invalid"`, `"empty"`)
- `lastError` (`string`, required, nullable)
- `undoable` (`boolean`, required): Whether the undo call would remove anything.
- `createdAt` (`string`, required, format `date-time`)
- `startedAt` (`string`, required, nullable, format `date-time`)
- `finishedAt` (`string`, required, nullable, format `date-time`)
- `undoneAt` (`string`, required, nullable, format `date-time`)

#### `ContactImportList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`ContactImport[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): An opaque cursor for the next page, or null on the last page. Pass it back unchanged.

#### `ContactList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`Contact[]`)
- `hasMore` (`boolean`)
- `nextCursor` (`string`, nullable)

#### `ContactSignUp`

`object`

- `id` (`string`): The submission id, `fsb_` plus 24 hex. `GET /forms/{id}/submissions/{submissionId}` reads it whole.
- `formId` (`string`): The form the person filled in, `frm_` plus 24 hex.
- `formName` (`string`): What the workspace calls that form.
- `status` (`string`, one of `"pending"`, `"added"`): `added` once the person is a contact in the audiences. `pending` while a double opt-in form waits for them to confirm.
- `answers` (`ContactSignUpAnswer[]`): A snapshot of every answer, kept as it was even after the form changes.
- `sourceUrl` (`string`, nullable): The page the form was filled in on, its origin and path only, when it was known.
- `createdAt` (`string`, format `date-time`): When the person sent the form.
- `confirmedAt` (`string`, nullable, format `date-time`): When the person joined the audiences. Null while pending.

#### `ContactSignUpAnswer`

`object`

- `key` (`string`): The field key the value was posted under.
- `label` (`string`): The field label when the person signed up.
- `type` (`string`, one of `"email"`, `"text"`, `"textarea"`, `"number"`, `"phone"`, `"url"`, `"date"`, `"select"`, `"radio"`, `"checkboxes"`, `"checkbox"`, `"consent"`, `"audiences"`, `"hidden"`, `"heading"`, `"paragraph"`, `"divider"`): The kind of field that was answered.
- `value` (`string | string[] | boolean`): Text for most fields, a list for multiple choice, true or false for a checkbox.
- `display` (`string`): The value as a person reads it, option labels included.

#### `ContactThread`

`object`

- `object` (`string`, one of `"thread"`)
- `id` (`string`): The id `GET /threads/{id}` takes.
- `subject` (`string`, nullable)
- `from` (`object`, nullable): Who sent the newest message.
  - `name` (`string`, nullable)
  - `email` (`string`)
- `receivedAt` (`string`, nullable): When the newest message arrived or went out.
- `messageCount` (`integer`)
- `hasUnread` (`boolean`)
- `labels` (`object[]`)
  - `id` (`string`)
  - `name` (`string`)

#### `ContactThreadList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`ContactThread[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): Opaque. Pass it back as `cursor` for the next page, and never build one yourself.

#### `DeletedContact`

`object`

- `object` (`string`, one of `"contact"`)
- `email` (`string`)
- `deleted` (`boolean`, one of `true`)
- `wasSaved` (`boolean`): True when a saved contact was removed, false when the address was only seen in mail and has now been hidden.

#### `DeletedContactCard`

`object`

- `object` (`string`, required, one of `"contact_card"`)
- `id` (`string`, required)
- `deleted` (`boolean`, required, one of `true`)

#### `Person`

`object`

- `object` (`string`, one of `"person"`)
- `email` (`string`): Lower cased. The key every contact route takes.
- `displayEmail` (`string`): The address as the most recent message wrote it, which may carry capitals.
- `name` (`string`, nullable): The saved name, or else the newest display name seen in mail.
- `saved` (`boolean`): True when the address is a saved contact.
- `source` (`string`, nullable, one of `"manual"`, `"auto"`, `"form"`): As on a contact, or null when the address is only seen in mail.
- `notes` (`string`, nullable)
- `attributes` (`Record<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.
- `photoUrl` (`string`, nullable)
- `threads` (`integer`, nullable): How 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.
- `lastAt` (`string`, nullable, format `date-time`): When the newest of those threads last moved. Null for a saved contact never seen in mail.
- `createdAt` (`string`, nullable, format `date-time`)
- `updatedAt` (`string`, nullable, format `date-time`)
- `blockedBy` (`object`, nullable): The workspace blocklist rule that blocks this address, or null when none does.
  - `rule` (`string`): The entry on the workspace blocklist that matches this address, exactly as it is stored.
  - `list` (`string`, one of `"blockedSenders"`, `"blockedDomains"`): `blockedSenders` for an address or pattern rule, `blockedDomains` for a rule that blocks a whole domain and every subdomain under it.

#### `PersonList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`Person[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): Opaque. Pass it back as `cursor` for the next page, and never build one yourself.
- `seen` (`boolean`): True when the addresses seen in mail are included, which needs `threads:read`. False means the rows are the saved contacts alone.
