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
Section titled “Message body fields”{ "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}textis the decoded sourcetext/plainMIME part, ornullwhen none exists.htmlis the decoded sourcetext/htmlMIME part, ornullwhen none exists.extracted_textis a nullable, best-effort derivative oftextwith quoted history and signatures removed.extracted_htmlis a nullable, best-effort derivative ofhtmlwith 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.
Read one message
Section titled “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
Section titled “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
Section titled “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:
- List or search thread summaries and keep the returned
thread_id. - Get that thread and reason over its oldest-first
messagesarray. Preferextracted_textfor concise reading, falling back to sourcetextwhen extraction is absent or source fidelity matters. - Reply with
thread_id. The server selects the newest message as the parent and derives recipients, subject,In-Reply-To, andReferencesat submission. When stale context would make the reply unsafe, also send the thread’slast_message_idasexpected_last_message_id; a409means 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
Section titled “Read state, folders, and batch updates”Read state is the IMAP \Seen flag. Messages live in one folder rather than carrying overlapping
labels.
mark_readorinbox.markRead(...)changes one message’s read state.batch_update_messageschanges read state and/or moves up to 200 message ids in one call.- Supported batch destinations are
INBOX,Sent,Trash,Junk, andArchive. - Malformed or unowned ids are returned in the batch’s
failedlist 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
Section titled “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
Section titled “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.