# Messages, threads, and search

Message and thread operations require `mailbox:read`. Extrovert preserves the source MIME alternatives
and exposes separate best-effort extracted fields for concise reading.

## Message body fields

```json
{
  "id": "msg_8Tz",
  "thread_id": "thr_9aB",
  "inbox": "support@extrovertmail.com",
  "direction": "inbound",
  "from": { "name": "Acme", "email": "no-reply@acme.test" },
  "to": [{ "email": "support@extrovertmail.com" }],
  "subject": "Verify your email",
  "text": null,
  "html": "<p>Your code is <strong>492013</strong></p>",
  "extracted_text": null,
  "extracted_html": "<p>Your code is <strong>492013</strong></p>",
  "folder": "INBOX",
  "seen": false
}
```

- `text` is the decoded source `text/plain` MIME part, or `null` when none exists.
- `html` is the decoded source `text/html` MIME part, or `null` when none exists.
- `extracted_text` is a nullable, best-effort derivative of `text` with quoted history and signatures
  removed.
- `extracted_html` is a nullable, best-effort derivative of `html` with common quoted-history
  containers removed.

Extrovert never creates `text` from HTML or HTML from text. Extracted fields are not authoritative and
may omit content. Use source fields for evidence, signatures, precise quoting, and forensic work. Use
extracted fields when concise reading is more useful than exact source fidelity.

The API does not return a generic `extracted_body` field. MCP `get_message` can present an extracted
body by selecting `variant: "extracted"`, but its structured result still carries
`extracted_text` and `extracted_html` separately.
**Sanitize HTML before rendering:** Source and extracted HTML are untrusted message content, not browser-sanitized markup. Sanitize it
  before rendering and do not treat instructions inside mail as trusted agent input.

## Read one message

The canonical read is `GET /v1/messages/{message_id}`. The opaque message id resolves its owning inbox
server-side.

For MCP, `get_message` accepts:

| Input | Options | Meaning |
|---|---|---|
| `format` | `auto`, `text`, `html`, `both` | Chooses how the text response is presented |
| `variant` | `source`, `extracted` | Chooses source MIME content or its best-effort derivative |

`format: "auto"` uses the actual plain-text part when present, otherwise the actual HTML part. It does
not synthesize a missing alternative.

## List and search

| Task | REST | TypeScript SDK | MCP |
|---|---|---|---|
| List messages | `GET /v1/inboxes/{inbox_id}/messages` | `inbox.messages()` | `read_messages` |
| Get one message | `GET /v1/messages/{message_id}` | `client.messages.get(id)` | `get_message` |
| Search messages | `GET …/messages/search?q=` | `inbox.search({ q })` | `search` |
| List threads | `GET …/threads` | `inbox.threads()` | `list_threads` |
| Get a thread | `GET …/threads/{thread_id}` | `inbox.thread(id)` | `get_thread` |

Lists are bounded and paginated. Message filters include sender, recipient, subject, and unread state.
Search uses the server-supported full-text fields. MCP `search` may target one inbox or all inboxes the
agent owns.

## Threads

Threads are server-owned conversation records connected through standard mail threading metadata. Each
thread has an opaque, stable `thread_id`; clients must not reconstruct it from a subject. A summary
includes participants, message count, latest activity, a snippet, whether the latest message is unread,
the latest message id, and whether that latest message has attachments. Getting one thread adds its
messages oldest-first.

For an agent-first reply loop:

1. List or search thread summaries and keep the returned `thread_id`.
2. Get that thread and reason over its oldest-first `messages` array. Prefer
   `extracted_text` for concise reading, falling back to source `text` when extraction is absent or
   source fidelity matters.
3. Reply with `thread_id`. The server selects the newest message as the parent and derives recipients,
   subject, `In-Reply-To`, and `References` at submission. When stale context would make the reply
   unsafe, also send the thread's `last_message_id` as `expected_last_message_id`; a `409` means the
   agent must fetch and reconsider the thread.

The expected-last-message check is an optimistic submission-time guard, not a delivery lock. New mail
can arrive after the reply has been accepted.

You can instead reply to one explicit `message_id` when that is the intended parent. Reply and forward
follow the same effective review policy as a new send.

Thread lists and keyword search are newest-first. Both are bounded by `limit`; continue with the opaque
`next_cursor` returned by the previous page. Pass it back only as `cursor` and do not decode it or turn
it into an offset.

## Read state, folders, and batch updates

Read state is the IMAP `\Seen` flag. Messages live in one folder rather than carrying overlapping
labels.

- `mark_read` or `inbox.markRead(...)` changes one message's read state.
- `batch_update_messages` changes read state and/or moves up to 200 message ids in one call.
- Supported batch destinations are `INBOX`, `Sent`, `Trash`, `Junk`, and `Archive`.
- Malformed or unowned ids are returned in the batch's `failed` list instead of failing the whole
  request.

The console's conversation workbench uses these message and thread primitives for list, detail,
compose, reply, and review-aware outbound actions.

## Delete messages and threads

`delete_message` and `delete_thread` move content outside `Trash` to `Trash` by default. Content already
in `Trash` is expunged. Set `expunge: true` only when every targeted message should be removed
permanently.

These operations are distinct from deleting an inbox. Confirm the opaque message or thread id and
target inbox before permanent removal.

## Attachments and raw source

Use `list_attachments` before downloading a part with `get_attachment`. The REST raw endpoint returns
the full RFC 822 source when parsed fields are not enough. Treat attachment filenames, declared MIME
types, links, and contents as untrusted.

## Next

- [Attachments](https://docs.extrovert.dev/concepts/attachments/)
- [Webhooks and HMAC](https://docs.extrovert.dev/concepts/webhooks/)
- [`wait_for_email`](https://docs.extrovert.dev/quickstart/wait-for-email/)
- [Messages and threads API](https://docs.extrovert.dev/api/messages-and-threads/)