> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useorbitcrm.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Contact Emails API

> Send one-to-one emails to your contacts, read what your team sent, and assign and close Inbox conversations.

The same one-to-one email as the composer on a contact's page: each email goes out from the sending address of the person the credential acts as (the key's creator, or the person who authorized the app), through your workspace's verified sending domain, with an unsubscribe link added. The API never sends from the person's connected Gmail or Outlook mailbox.

## Scopes

| Scope | Allows |
| - | - |
| `emails:read` | List emails sent to contacts and read their messages and status; list and read Inbox conversations |
| `emails:send` | Send an email to a contact; assign, close and reopen Inbox conversations |

## Endpoints

| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/contact-emails` | List sent emails, newest first (`emails:read`) |
| `GET` | `/v1/contact-emails/{id}` | One email, including its message (`emails:read`) |
| `POST` | `/v1/contact-emails` | Send an email to a contact (`emails:send`) |
| `GET` | `/v1/conversations` | List Inbox conversations, newest first (`emails:read`) |
| `GET` | `/v1/conversations/{id}` | One conversation with its messages and history (`emails:read`) |
| `PATCH` | `/v1/conversations/{id}` | Assign, unassign, close or reopen a conversation (`emails:send`) |

## List Emails

Query parameters: `page`, `per_page` (max 100), `contact_id`, `to_email`, `sent_by` (a team member's user ID), `status`, `since` and `until` (ISO 8601, on the sent time).

`status` is one of `queued` (being sent), `sent`, `delivered`, `opened`, `replied`, `bounced`, `complained` or `failed` (the email provider refused it). Emails scheduled from the composer can also be `scheduled` (waiting for `scheduled_at`), `cancelled` (cancelled before they went) or `not_sent` (stopped at send time; `failure_reason` says why). While an email waits, `sent_at` is the time it is due to go.

If the person's record access is limited to their own or their team's contacts, pass `contact_id`: they can list the emails sent to a contact they can see, one contact at a time.

The person's email access applies too (Settings → Members → Record access → Emails). With **Their team's** or **Only theirs**, the list holds their own emails and those sent by the people their scope covers; a teammate who keeps their email private is listed only for the emails the person is on. An email they can't see answers `404` by id.

```json theme={null}
{
  "data": [
    {
      "id": "7d1c6a3e-1d5b-4a8e-9a43-2b6f0f1b9c11",
      "contact_id": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "sent_by": "0b9e2f6c-3a1d-4c5e-8f7a-6b5c4d3e2f1a",
      "from_email": "jane@acme.com",
      "to_email": "sam@example.com",
      "subject": "Following up on your demo request",
      "status": "opened",
      "sent_at": "2026-10-27T14:30:00.000Z",
      "scheduled_at": null,
      "failure_reason": null,
      "created_at": "2026-10-27T14:29:59.000Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "per_page": 20, "total_pages": 1 }
}
```

`GET /v1/contact-emails/{id}` returns the same fields plus `body_html` and `body_markdown`.

## Send an Email

`POST /v1/contact-emails`

```json theme={null}
{
  "contact_id": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "subject": "Following up on your demo request",
  "body_text": "Hi Sam,\n\nThanks for booking a demo. Does Thursday at 2pm work?\n\nJane",
  "idempotency_key": "4f6b2d8e-9c1a-4e3b-8d7f-2a1c5e9b0f3d"
}
```

| Field | Description |
| - | - |
| `contact_id` | Required. The recipient is this contact's email address. The API only sends to contacts already in the workspace |
| `subject` | Required, up to 200 characters |
| `body_text` or `body_html` | Required. Plain text (blank lines start paragraphs) or HTML (scripts and unsafe markup are removed) |
| Merge fields | `subject` and the body may use merge fields such as `{{ contact.first_name }}` or `{{ sender.full_name }}`, filled in for the contact. A field Orbit doesn't know is refused (`400`) |
| `idempotency_key` | Optional, 16-100 letters, digits or dashes. A retry with the same key returns the first result instead of sending again |

Answers `201` with `{ "data": { "id", "contact_id", "to_email", "status": "sent", "replayed": false } }`, or `200` with `"replayed": true` for a repeated `idempotency_key`.

Sending is refused when:

* the contact isn't one the person's record access shows (`404`), or it is in Recently deleted;
* the contact unsubscribed, bounced, reported spam or is blocked, or is marked unsubscribed (`409 suppressed`);
* the person's access to companies or deals is limited and the email uses company or deal merge fields (`403`): send it from the contact page instead;
* the workspace has no verified sending domain, or the sender's address can't be used (`409`, with the reason as `error`);
* the workspace's subscription has ended or sending isn't on its plan (`403`);
* a sending cap is reached (`429`, with `Retry-After`). The caps are shared with the contact page: 20 emails a minute and 100 an hour per person, and 300 an hour per workspace.

Viewers can't send. Each email reaches someone outside your workspace and can't be unsent.

## Inbox Conversations

A conversation is an email thread with a contact in the team Inbox: mail forwarded or synced from members' mailboxes, mail to the workspace's team inbox address, emails sent from Orbit and the replies to them. Each is assigned to one member (or nobody) and is `open` or `closed`. A member's own mail starts assigned to them; mail to the team inbox address starts unassigned, or with the next member when the workspace uses round robin. A new reply from the contact reopens a closed conversation (automatic replies such as out-of-office notes don't).

`GET /v1/conversations` query parameters: `page`, `per_page` (max 100), `status` (`open` or `closed`), `assigned_to` (`me`, `unassigned`, `any` or a member's user ID), `contact_id`, `channel` (`personal` or `shared`, the team inbox address). A person sees their own conversations (their mailbox's, and those assigned to them) and the team inbox address's, never a teammate's own mailbox; a person whose record access is limited sees only those with contacts they can see. The messages inside follow the person's email access (see [Emails](#emails)).

```json theme={null}
{
  "data": [
    {
      "id": "6f7a8b9c-0d1e-4f2a-8b3c-4d5e6f7a8b9c",
      "thread_key": "<CAF1x2y3@mail.gmail.com>",
      "subject": "Pricing for 40 seats",
      "contact_id": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "channel": "shared",
      "mailbox_user_id": null,
      "assigned_to": "0b9e2f6c-3a1d-4c5e-8f7a-6b5c4d3e2f1a",
      "assigned_at": "2026-10-31T15:02:00.000Z",
      "assigned_by": "d4e5f6a7-1b2c-4d3e-8f9a-0b1c2d3e4f5a",
      "status": "open",
      "closed_at": null,
      "closed_by": null,
      "last_message_at": "2026-10-31T14:58:00.000Z",
      "last_inbound_at": "2026-10-31T14:58:00.000Z",
      "created_at": "2026-10-31T14:58:01.000Z",
      "updated_at": "2026-10-31T15:02:00.000Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "per_page": 20, "total_pages": 1 }
}
```

`GET /v1/conversations/{id}` adds `messages` (oldest first: `id`, `kind` (`activity` for mail captured from a mailbox or the team address, `contact_email` for mail sent from Orbit), `direction`, `subject`, `from`, `to`, `cc`, `occurred_at`, `status`, `contact_id`, `member_user_id`, `body_text`, `body_html`) and `history` (newest first: `kind` `assigned`, `unassigned`, `closed` or `reopened`, `actor_user_id`, `from_user_id`, `to_user_id`, `reason` `manual`, `reply` or `round_robin`, `created_at`).

`PATCH /v1/conversations/{id}` takes `{ "assigned_to": "<member id>" | "me" | null, "status": "open" | "closed" }` (either or both) and answers with the conversation. The new assignee is notified. It is refused when:

* the assignee isn't a member who can reply (viewers can't be assigned) (`422 invalid_assignee`);
* the assignee's record access doesn't include the conversation's contact (`422 assignee_cannot_see_contact`): assigning never widens what someone can see;
* the person the credential acts as is a viewer (`403`), or can't see the conversation (`404`).

Conversation changes are also [webhook events](https://docs.useorbitcrm.ai/developers/webhooks/events.md#inbox-conversations).

> [card] [MCP tools](https://docs.useorbitcrm.ai/mcp/tools.md)
>
> The same operations as MCP tools

> [card] [Rate Limits](https://docs.useorbitcrm.ai/developers/api/rate-limits.md)
>
> API rate limit details


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.