Skip to content

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.

{
"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.

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

For MCP, get_message accepts:

InputOptionsMeaning
formatauto, text, html, bothChooses how the text response is presented
variantsource, extractedChooses 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.

TaskRESTTypeScript SDKMCP
List messagesGET /v1/inboxes/{inbox_id}/messagesinbox.messages()read_messages
Get one messageGET /v1/messages/{message_id}client.messages.get(id)get_message
Search messagesGET …/messages/search?q=inbox.search({ q })search
List threadsGET …/threadsinbox.threads()list_threads
Get a threadGET …/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 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 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_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.

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.