# Get the body of an email and its whole thread

Source: https://docs.emelia.io/docs/inbox-api/get-body-and-thread

Guide: [Inbox API](https://docs.emelia.io/docs/inbox-api.md)

`GET https://api.emelia.io/inbox/api/v1/emails/{emailId}/body`

Returns the HTML and text body of the requested email, plus every message of its conversation, oldest first: messages received, messages you sent (`isMe: true`) and drafts in progress. The thread is resolved from the provider thread id when there is one, otherwise from the `Message-ID`, `In-Reply-To` and `References` headers, and is bounded to the mailbox of the email. Works with a draft id too.

Inline images (`cid:`) are rewritten to presigned storage URLs valid 15 minutes.

Known issue: this response is not passed through the public serializer. Thread messages may also contain internal fields that are not part of the contract: do not rely on them, they may be removed. An unknown id returns `NOT_FOUND`, not `EMAIL_NOT_FOUND`.

## Authentication

Send your API key in the `Authorization` header: `Authorization: <your API key>`.

## Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `emailId` | string | yes | Inbox id of the email (from `GET /emails`, webhooks...). |

## Example request

**cURL**

```bash
curl -X GET "https://api.emelia.io/inbox/api/v1/emails/cmemacme0000000000000001/body" \
  -H "Authorization: $EMELIA_API_KEY"
```

**JavaScript**

```js
const response = await fetch('https://api.emelia.io/inbox/api/v1/emails/cmemacme0000000000000001/body', {
  method: 'GET',
  headers: { Authorization: process.env.EMELIA_API_KEY },
});
console.log(await response.json());
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.emelia.io/inbox/api/v1/emails/cmemacme0000000000000001/body",
    headers={"Authorization": os.environ["EMELIA_API_KEY"]},
)
print(response.json())
```

## Responses

### 200: Body and thread.

| Field | Type | Required | Description |
|---|---|---|---|
| `emailId` | string | yes |  |
| `html` | string | no | Body of the requested email, if any. |
| `text` | string | no |  |
| `thread` | array of object | yes | Oldest first. |
| `thread[].id` | string | yes |  |
| `thread[].mailboxId` | string | yes |  |
| `thread[].messageId` | string | yes |  |
| `thread[].references` | array of string | yes |  |
| `thread[].fromAddress` | string | yes |  |
| `thread[].fromName` | string | no |  |
| `thread[].toAddresses` | array of object | yes |  |
| `thread[].ccAddresses` | array of object | no |  |
| `thread[].bccAddresses` | array of object | no |  |
| `thread[].date` | string (date-time) | yes |  |
| `thread[].subject` | string | yes |  |
| `thread[].html` | string | no | HTML body; inline images rewritten to 15 minute URLs. |
| `thread[].text` | string | no |  |
| `thread[].isMe` | boolean | yes | Sent from the mailbox address itself. |
| `thread[].isDraft` | boolean | no | Present only when true. |
| `thread[].isAutoReply` | boolean | no | Present only when true. |
| `thread[].isAutoForward` | boolean | no | Present only when true. |
| `thread[].attachmentsS3` | array of object | no | Internal field, not part of the contract (may be removed). |
| `thread[].magicReplyConfidence` | number | no | Internal field, not part of the contract (may be removed). |
| `thread[].magicReplyReasoning` | string | no | Internal field, not part of the contract (may be removed). |
| `thread[].magicReplyContextSource` | enum: `label`, `global` | no | Internal field, not part of the contract (may be removed). |

```json
{
  "emailId": "cmemacme0000000000000001",
  "html": "<div>Thanks John, could we schedule a call next week?</div>",
  "text": "Thanks John, could we schedule a call next week?",
  "thread": [
    {
      "id": "cmemacme0000000000000002",
      "mailboxId": "cmbxacme0000000000000001",
      "messageId": "<20261008091500.acme01@acme.com>",
      "references": [],
      "fromAddress": "john.doe@acme.com",
      "fromName": "John Doe",
      "toAddresses": [
        {
          "email": "jane.roe@example.com",
          "name": "Jane Roe"
        }
      ],
      "date": "2026-10-06T08:00:00.000Z",
      "subject": "Partnership with Acme",
      "html": "<div>Hi Jane, would a partnership make sense?</div>",
      "text": "Hi Jane, would a partnership make sense?",
      "isMe": true
    },
    {
      "id": "cmemacme0000000000000001",
      "mailboxId": "cmbxacme0000000000000001",
      "messageId": "<CAJacme.0001@mail.example.com>",
      "references": [
        "<20261008091500.acme01@acme.com>"
      ],
      "fromAddress": "jane.roe@example.com",
      "fromName": "Jane Roe",
      "toAddresses": [
        {
          "email": "john.doe@acme.com"
        }
      ],
      "date": "2026-10-08T09:12:44.000Z",
      "subject": "Re: Partnership with Acme",
      "html": "<div>Thanks John, could we schedule a call next week?</div>",
      "text": "Thanks John, could we schedule a call next week?",
      "isMe": false,
      "attachmentsS3": [
        {
          "filename": "brief.pdf",
          "s3Key": "emails/acme-example/attachments/brief.pdf",
          "size": 48213,
          "mimeType": "application/pdf"
        }
      ]
    }
  ]
}
```

### 401: Missing, malformed, unknown or disabled key. No `X-RateLimit-*` headers.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | enum: `INVALID_API_KEY`, `WORKSPACE_SUSPENDED`, `FORBIDDEN`, `PLAN_REQUIRED`, `VALIDATION_FAILED`, `REQUEST_ERROR`, `PAYLOAD_TOO_LARGE`, `NOT_FOUND`, `EMAIL_NOT_FOUND`, `DRAFT_NOT_FOUND`, `MAILBOX_NOT_FOUND`, `LABEL_NOT_FOUND`, `VIEW_NOT_FOUND`, `RULE_NOT_FOUND`, `FILTER_NOT_FOUND`, `SIGNATURE_NOT_FOUND`, `MAILBOX_INACTIVE`, `INVALID_SENDER_ALIAS`, `NO_TRASH_FOLDER`, `INVALID_TAG`, `CONFLICT`, `DUPLICATE_TAG_NAME`, `DUPLICATE_RULE`, `DUPLICATE_FILTER`, `SCHEDULED_SEND_IN_PROGRESS`, `DRAFT_ALREADY_EXISTS`, `QUERY_TIMEOUT`, `RATE_LIMIT_EXCEEDED`, `QUOTA_EXCEEDED`, `INTERNAL_ERROR` | yes | Codes that the Inbox API can return, as found in the code. Branch on `code`, never on `error`. The HTTP status is derived from the code (unknown business codes are 400). |
| `error` | string | yes | Human readable. |
| `details` | object | no | Optional context: faulty fields, bounds, ids. |

```json
{
  "code": "INVALID_API_KEY",
  "error": "string",
  "details": {}
}
```

### 403: `WORKSPACE_SUSPENDED`: the Emelia subscription has expired.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | enum: `INVALID_API_KEY`, `WORKSPACE_SUSPENDED`, `FORBIDDEN`, `PLAN_REQUIRED`, `VALIDATION_FAILED`, `REQUEST_ERROR`, `PAYLOAD_TOO_LARGE`, `NOT_FOUND`, `EMAIL_NOT_FOUND`, `DRAFT_NOT_FOUND`, `MAILBOX_NOT_FOUND`, `LABEL_NOT_FOUND`, `VIEW_NOT_FOUND`, `RULE_NOT_FOUND`, `FILTER_NOT_FOUND`, `SIGNATURE_NOT_FOUND`, `MAILBOX_INACTIVE`, `INVALID_SENDER_ALIAS`, `NO_TRASH_FOLDER`, `INVALID_TAG`, `CONFLICT`, `DUPLICATE_TAG_NAME`, `DUPLICATE_RULE`, `DUPLICATE_FILTER`, `SCHEDULED_SEND_IN_PROGRESS`, `DRAFT_ALREADY_EXISTS`, `QUERY_TIMEOUT`, `RATE_LIMIT_EXCEEDED`, `QUOTA_EXCEEDED`, `INTERNAL_ERROR` | yes | Codes that the Inbox API can return, as found in the code. Branch on `code`, never on `error`. The HTTP status is derived from the code (unknown business codes are 400). |
| `error` | string | yes | Human readable. |
| `details` | object | no | Optional context: faulty fields, bounds, ids. |

```json
{
  "code": "INVALID_API_KEY",
  "error": "string",
  "details": {}
}
```

### 404: `NOT_FOUND`: the email does not exist, or belongs to another workspace (never 403, to prevent enumeration).

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | enum: `INVALID_API_KEY`, `WORKSPACE_SUSPENDED`, `FORBIDDEN`, `PLAN_REQUIRED`, `VALIDATION_FAILED`, `REQUEST_ERROR`, `PAYLOAD_TOO_LARGE`, `NOT_FOUND`, `EMAIL_NOT_FOUND`, `DRAFT_NOT_FOUND`, `MAILBOX_NOT_FOUND`, `LABEL_NOT_FOUND`, `VIEW_NOT_FOUND`, `RULE_NOT_FOUND`, `FILTER_NOT_FOUND`, `SIGNATURE_NOT_FOUND`, `MAILBOX_INACTIVE`, `INVALID_SENDER_ALIAS`, `NO_TRASH_FOLDER`, `INVALID_TAG`, `CONFLICT`, `DUPLICATE_TAG_NAME`, `DUPLICATE_RULE`, `DUPLICATE_FILTER`, `SCHEDULED_SEND_IN_PROGRESS`, `DRAFT_ALREADY_EXISTS`, `QUERY_TIMEOUT`, `RATE_LIMIT_EXCEEDED`, `QUOTA_EXCEEDED`, `INTERNAL_ERROR` | yes | Codes that the Inbox API can return, as found in the code. Branch on `code`, never on `error`. The HTTP status is derived from the code (unknown business codes are 400). |
| `error` | string | yes | Human readable. |
| `details` | object | no | Optional context: faulty fields, bounds, ids. |

```json
{
  "code": "INVALID_API_KEY",
  "error": "string",
  "details": {}
}
```

### 429: `RATE_LIMIT_EXCEEDED`: per minute limit of the workspace (wait `Retry-After`). `QUOTA_EXCEEDED`: monthly quota exhausted until the 1st of next month UTC (in Emelia the plan cannot be upgraded).

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | enum: `INVALID_API_KEY`, `WORKSPACE_SUSPENDED`, `FORBIDDEN`, `PLAN_REQUIRED`, `VALIDATION_FAILED`, `REQUEST_ERROR`, `PAYLOAD_TOO_LARGE`, `NOT_FOUND`, `EMAIL_NOT_FOUND`, `DRAFT_NOT_FOUND`, `MAILBOX_NOT_FOUND`, `LABEL_NOT_FOUND`, `VIEW_NOT_FOUND`, `RULE_NOT_FOUND`, `FILTER_NOT_FOUND`, `SIGNATURE_NOT_FOUND`, `MAILBOX_INACTIVE`, `INVALID_SENDER_ALIAS`, `NO_TRASH_FOLDER`, `INVALID_TAG`, `CONFLICT`, `DUPLICATE_TAG_NAME`, `DUPLICATE_RULE`, `DUPLICATE_FILTER`, `SCHEDULED_SEND_IN_PROGRESS`, `DRAFT_ALREADY_EXISTS`, `QUERY_TIMEOUT`, `RATE_LIMIT_EXCEEDED`, `QUOTA_EXCEEDED`, `INTERNAL_ERROR` | yes | Codes that the Inbox API can return, as found in the code. Branch on `code`, never on `error`. The HTTP status is derived from the code (unknown business codes are 400). |
| `error` | string | yes | Human readable. |
| `details` | object | no | Optional context: faulty fields, bounds, ids. |

```json
{
  "code": "INVALID_API_KEY",
  "error": "string",
  "details": {}
}
```

### 500: `INTERNAL_ERROR`: unexpected failure. The message is generic in production.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | enum: `INVALID_API_KEY`, `WORKSPACE_SUSPENDED`, `FORBIDDEN`, `PLAN_REQUIRED`, `VALIDATION_FAILED`, `REQUEST_ERROR`, `PAYLOAD_TOO_LARGE`, `NOT_FOUND`, `EMAIL_NOT_FOUND`, `DRAFT_NOT_FOUND`, `MAILBOX_NOT_FOUND`, `LABEL_NOT_FOUND`, `VIEW_NOT_FOUND`, `RULE_NOT_FOUND`, `FILTER_NOT_FOUND`, `SIGNATURE_NOT_FOUND`, `MAILBOX_INACTIVE`, `INVALID_SENDER_ALIAS`, `NO_TRASH_FOLDER`, `INVALID_TAG`, `CONFLICT`, `DUPLICATE_TAG_NAME`, `DUPLICATE_RULE`, `DUPLICATE_FILTER`, `SCHEDULED_SEND_IN_PROGRESS`, `DRAFT_ALREADY_EXISTS`, `QUERY_TIMEOUT`, `RATE_LIMIT_EXCEEDED`, `QUOTA_EXCEEDED`, `INTERNAL_ERROR` | yes | Codes that the Inbox API can return, as found in the code. Branch on `code`, never on `error`. The HTTP status is derived from the code (unknown business codes are 400). |
| `error` | string | yes | Human readable. |
| `details` | object | no | Optional context: faulty fields, bounds, ids. |

```json
{
  "code": "INVALID_API_KEY",
  "error": "string",
  "details": {}
}
```
