# Find mail

Search words and phrases, combine message filters, and find matching conversations.

Source: https://cherami.to/docs/guides/search



Use the ordinary received, sent or conversation listing to find mail. Search and filters work together in one request; no separate search endpoint is needed.

```bash
curl --get 'https://cherami.to/v1/inboxes/INBOX_ID/messages' \
  -H "Authorization: Bearer $CHERAMI_API_KEY" \
  --data-urlencode 'query="project invoice"' \
  --data-urlencode 'from=alice@example.com' \
  --data-urlencode 'labels_all=needs-attention' \
  --data-urlencode 'order=relevance'
```

With MCP, use `list_messages`, `list_sent` or `list_threads` with the same named filters. `count_messages` accepts the same filters as the received listing, without ordering or pagination.

With the [TypeScript SDK](https://cherami.to/docs/typescript), the same filters remain ordinary listing parameters:

```ts
for await (const message of client.iterate("listMessages", {
  inbox_id: inboxId,
  query: '"project invoice"',
  from: "alice@example.com",
  labels_all: ["needs-attention"],
  order: "relevance",
}, { maxPages: 5 })) {
  console.log(message.id);
}
```

Use `client.pages` when you need the last page's `next_cursor` to resume a bounded scan. `countMessages` takes the received filters without pagination or order.

With the [Python SDK](https://cherami.to/docs/python), dictionary keys preserve HTTP filter names, including `from`:

```python
for message in client.iterate("list_messages", {
    "inbox_id": inbox_id,
    "query": '"project invoice"',
    "from": "alice@example.com",
    "labels_all": ["needs-attention"],
    "order": "relevance",
}, max_pages=5):
    print(message["id"])
```

With `AsyncCherami`, use `async for`. Python's `pages` and `count_messages` work like their TypeScript counterparts above.

## Search words and phrases [#search-words-and-phrases]

`query` searches decoded subjects and message bodies. When a message has no usable plain-text alternative, search uses text extracted from HTML. Attachment contents, attachment filenames and transport headers are not searched. Quoted history remains searchable.

All query terms must match the same message, but may occur in different parts of its subject or body. Double quotes require consecutive words in one subject or body. For example, `invoice "repair workshop"` requires both `invoice` and the phrase `repair workshop`.

Matching is case-insensitive and ignores Latin diacritics. It matches whole tokens, not arbitrary substrings or word stems. Punctuation separates tokens; `OR`, `NOT` and wildcards have no special meaning. For a literal subject substring, use `subject` instead.

Search matches words and phrases, not semantic similarity. If you need semantic search, contact [hello@cherami.to](mailto:hello@cherami.to) and tell us what you're trying to find.

## Combine filters [#combine-filters]

All supplied conditions combine with AND. Omit a condition to leave it unrestricted.

| Parameter     | Meaning                                                                                                              |
| ------------- | -------------------------------------------------------------------------------------------------------------------- |
| `query`       | Words and double-quoted phrases in subject/body. Up to 512 characters and 16 terms or phrases.                       |
| `from`        | Exact bare email address in From, case-insensitive. This is not the SMTP envelope sender or proof of identity.       |
| `recipient`   | Exact bare email address in To/Cc/Bcc where present, case-insensitive. Bcc is commonly absent from received headers. |
| `subject`     | Literal case-insensitive subject substring, up to 998 characters. `%` and `_` are literal characters.                |
| `after`       | Inclusive service receipt/submission timestamp.                                                                      |
| `before`      | Exclusive service receipt/submission timestamp.                                                                      |
| `labels_all`  | Require every listed label.                                                                                          |
| `labels_any`  | Require at least one listed label.                                                                                   |
| `labels_none` | Exclude messages carrying any listed label.                                                                          |

Use `YYYY-MM-DDTHH:mm:ss` followed by `Z` or a numeric offset such as `+02:00`. Optional fractional seconds have one to three digits: `2026-10-01T00:00:00.123Z`. Date-only values and times without a timezone are not accepted. URL-encode offsets in HTTP queries so `+` is not interpreted as a space.

Bounds use received time for incoming mail and submission time for outgoing mail, never the sender's Date header. `after` must precede `before` when both are supplied. To select a local calendar day, supply its start and the next day's start as `after` and `before`, each with the offset applicable at that boundary; daylight-saving transitions can make the interval shorter or longer than 24 hours.

In HTTP, repeat label parameters for multiple labels; in MCP and SDKs, supply arrays. Label matching is case-sensitive. See [label filtering](https://cherami.to/docs/guides/labels) and the [listing parameter reference](https://cherami.to/docs/api/messages/list-messages#parameters) for validation limits.

Text, sender and recipient filters match prepared received mail only. Pending, processing and failed messages remain available without those filters, including with date/label filters. Sent searches include accepted, rejected and unknown attempts; a match is not delivery confirmation.

## Choose an order [#choose-an-order]

| `order`     | Result order                                                                             |
| ----------- | ---------------------------------------------------------------------------------------- |
| `newest`    | Newest first; the default.                                                               |
| `oldest`    | Oldest first.                                                                            |
| `relevance` | Strongest keyword matches first; requires `query`. Subject matches receive extra weight. |

Relevance ties use newest first. For conversations, newest/oldest refers to the whole conversation's latest activity, not just its matching messages.

Follow `next_cursor` with the same filters and order. Results can change while you paginate; restart without a cursor for a fresh view.

## Find a conversation [#find-a-conversation]

`list_threads` and `GET /v1/inboxes/{inbox_id}/threads` return a conversation when **one member satisfies every condition**. A sender match on one message and a keyword match on another do not qualify it. Label exclusions apply to that matching member, not every message in the conversation.

Filtered conversations include `matching_message_ids`, containing up to 100 matching IDs newest first, and `matching_message_count`, the total number of matching members. The ordinary conversation counts, subject and activity still describe the whole conversation. Read it with `get_thread` or the [thread detail endpoint](https://cherami.to/docs/api/threads/get-thread). Thread detail remains unfiltered.

[Received-message reference](https://cherami.to/docs/api/messages) · [Sent-message reference](https://cherami.to/docs/api/sending) · [Conversation reference](https://cherami.to/docs/api/threads)
