# Search emails by keyword

Source: https://docs.emelia.io/docs/inbox-api/search-emails-by-keyword

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

`GET https://api.emelia.io/inbox/api/v1/emails/search`

Full text style search on the subject, the sender name and address, the preview snippet and the recipient addresses. The full body is not searched. Matching is a case insensitive substring match.

Ranking: subject match first (with a bonus when the subject starts with the term), then sender (bonus on an exact sender match), snippet, recipients; ties are broken by date then id, most recent first.

Without `folderType`, every folder is searched. The inbox filters do not apply here: hidden emails can be returned.

The cursor of this endpoint has its own format (it starts with `s|`); pass it back unchanged. An unreadable cursor is ignored and the first page is returned again. Stop on `hasMore: false`.

## Authentication

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

## Query parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `q` | string | yes | Search term, 2 to 200 characters after trimming. Too short or missing: 400 with `details.q`. Too long: 400 with `details.search`. Min length 2. |
| `mailboxId` | string | no | Restrict to one mailbox (ids from `GET /mailboxes`). An unknown id returns an empty page. |
| `folderType` | enum: `INBOX`, `SENT`, `DRAFTS`, `SPAM`, `TRASH` | no | Restrict to one system folder. Case sensitive; any other value is rejected with 400. Omitted, every folder is considered. |
| `cursor` | string | no | Opaque `nextCursor` from the previous page. Its format is not part of the contract. |
| `limit` | integer | no | Page size. Above 200 it is clamped to 200; an unreadable or non positive value falls back to 50 (no error). Default: `50`. Range: 1 to 200. |

## Example request

**cURL**

```bash
curl -X GET "https://api.emelia.io/inbox/api/v1/emails/search?q=partnership" \
  -H "Authorization: $EMELIA_API_KEY"
```

**JavaScript**

```js
const response = await fetch('https://api.emelia.io/inbox/api/v1/emails/search?q=partnership', {
  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/search?q=partnership",
    headers={"Authorization": os.environ["EMELIA_API_KEY"]},
)
print(response.json())
```

## Responses

### 200: A page of matching emails.

| Field | Type | Required | Description |
|---|---|---|---|
| `items` | array of object | yes |  |
| `items[].id` | string | yes | Inbox id of the email. |
| `items[].mailboxId` | string | yes |  |
| `items[].folderId` | string | yes |  |
| `items[].messageId` | string | yes | RFC 5322 Message-ID header. Needed to reply in a thread. |
| `items[].threadId` | string or null | yes | Provider conversation id (Gmail, Microsoft), null for IMAP. |
| `items[].subject` | string | yes |  |
| `items[].from` | object | yes | An address as returned by the API. `name` is null when unknown. |
| `items[].to` | array of object | yes |  |
| `items[].cc` | array of object | yes |  |
| `items[].date` | string (date-time) | yes |  |
| `items[].snippet` | string | yes | Short text preview (about 200 characters). |
| `items[].isRead` | boolean | yes |  |
| `items[].isStarred` | boolean | yes |  |
| `items[].isDraft` | boolean | yes |  |
| `items[].waitingForReply` | boolean | yes | Estimate that a sent email still expects an answer. |
| `items[].hasAttachments` | boolean | yes |  |
| `items[].attachments` | array of object | yes |  |
| `items[].inReplyTo` | string or null | yes |  |
| `items[].references` | array of string | yes |  |
| `items[].tags` | array of object | yes | Inbox labels applied to the email (manual and AI). |
| `nextCursor` | string or null | yes | Cursor of the last item of the page; null only when the page is empty. Use `hasMore` to stop. |
| `hasMore` | boolean | yes |  |

```json
{
  "items": [
    {
      "id": "cmemacme0000000000000001",
      "mailboxId": "cmbxacme0000000000000001",
      "folderId": "cmfdacme0000000000000001",
      "messageId": "<CAJacme.0001@mail.example.com>",
      "threadId": "18c2acme0001",
      "subject": "Re: Partnership with Acme",
      "from": {
        "email": "jane.roe@example.com",
        "name": "Jane Roe"
      },
      "to": [
        {
          "email": "john.doe@acme.com",
          "name": null
        }
      ],
      "cc": [],
      "date": "2026-10-08T09:12:44.000Z",
      "snippet": "Thanks John, could we schedule a call next week?",
      "isRead": false,
      "isStarred": false,
      "isDraft": false,
      "waitingForReply": false,
      "hasAttachments": true,
      "attachments": [
        {
          "filename": "brief.pdf",
          "size": 48213,
          "mimeType": "application/pdf"
        }
      ],
      "tags": [
        {
          "id": "cmtgacme0000000000000002",
          "name": "Interested",
          "color": "#2563eb",
          "isAiLabel": true
        }
      ],
      "inReplyTo": "<20261008091500.acme01@acme.com>",
      "references": [
        "<20261008091500.acme01@acme.com>"
      ]
    }
  ],
  "nextCursor": "s|65|2026-10-08T09:12:44.000Z|cmemacme0000000000000001",
  "hasMore": false
}
```

### 400: `VALIDATION_FAILED`: `q` missing, shorter than 2 or longer than 200 characters, or unknown `folderType`.

| 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": {}
}
```

### 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": {}
}
```

### 408: `QUERY_TIMEOUT`: the query ran more than 10 seconds and was cancelled by the database.

| 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": {}
}
```
