Threads and messages
How MachineMail stores email as messages grouped into threads, how labels and read state work, what extracted_text and the trust report give your agent, and how to fetch attachments and raw MIME.
A message is one email, inbound or outbound. A thread is a conversation: the messages in one inbox that reply to each other. Every message belongs to exactly one thread, and every thread belongs to exactly one inbox.
Messages
Inbound messages carry the full parsed email plus a trust report. This is an inbound reply as returned by GET /v0/inboxes/{inbox_id}/messages/{message_id}, trimmed:
| Field | Notes |
|---|---|
message_id | msg_ prefix. MachineMail’s ID, not the RFC 5322 Message-ID header. |
timestamp | The Date header for inbound mail (arrival time if it is missing or invalid); send time for outbound. |
from | One string. to, cc, bcc and reply_to are arrays. |
preview | Short plain-text snippet of the body. |
text, html | The bodies as sent. Untrusted. |
extracted_text, extracted_html | Inbound only: the new part of the message, with quoted history and signatures stripped. |
in_reply_to, references | Threading headers, without angle brackets. |
headers | Message headers as a map. |
size | Bytes of the raw message. |
direction | inbound or outbound. |
delivery_status | Outbound only: sent, delivered, bounced or complained. |
trust_level | Inbound only: the trust report’s level, handy in lists. |
trust | Inbound only: the full trust report. |
received_via | raw when the message was injected with POST /inbound/raw. |
Lists return the short form (MessageItem): everything except text, html, extracted_*, reply_to, received_via and trust. trust_level is in both. Fetch the message to get bodies and the trust report.
What to give your agent
There are three versions of an inbound body. Pick deliberately:
trust.content.textis the agent-safe body: quoted history, signatures, disclaimers, hidden text, zero-width characters and tracking pixels removed. Give this to the model by default.extracted_textis the reply-parsed body: the new text without quoted history or signatures, but otherwise as written. Hidden text tricks are not removed.textandhtmlare the raw bodies. Never put them in a prompt unchecked.
Also check trust.level before acting on what a message asks. See Using trust in agents.
Threads
MachineMail threads messages by their headers. When a message arrives or is sent, its In-Reply-To and References are matched against the messages already in the same inbox, by RFC 5322 Message-ID and by MachineMail message IDs. A match puts it in that thread; otherwise it starts a new one. Subjects are not used for threading.
Replies sent with the reply endpoints set these headers for you, so the conversation stays in one thread on both sides. See Reply in thread.
A thread’s labels are the union of its messages’ labels. timestamp, preview and last_message_id follow the latest message. received_timestamp and sent_timestamp are the latest inbound and outbound times.
GET /v0/threads/{thread_id} returns the thread plus a messages array with full messages, oldest first, including trust reports.
| Method | Path | Notes |
|---|---|---|
GET | /v0/threads | Threads across every inbox in scope. |
GET | /v0/inboxes/{inbox_id}/threads | Threads in one inbox. |
GET | /v0/threads/{thread_id} | Thread with messages. Also under /v0/inboxes/{inbox_id}/threads/{thread_id}. |
PATCH | /v0/threads/{thread_id} | add_labels and remove_labels, applied to every message in the thread. |
DELETE | /v0/threads/{thread_id} | Deletes the thread and all its messages. |
GET | /v0/threads/search?q= | Full-text search, returns threads. Also per inbox. |
Labels
Labels are free strings on each message. MachineMail sets these system labels:
| Label | Set when |
|---|---|
INBOX | An inbound message is accepted normally. |
UNREAD | Every new inbound message. Remove it to mark the message read. |
SENT | Every outbound message. |
QUARANTINE | The trust layer quarantined the message. Replaces INBOX. |
SPAM | The spam probability is 0.9 or higher. Replaces INBOX. |
UNAUTHENTICATED | DMARC failed, or SPF and DKIM both failed. Added next to INBOX. |
BLOCKED | The sender matched a receive block list. The only label. |
TRASH is honoured as a hidden label but never set by MachineMail; deleting is permanent. Add your own labels freely, for example needs-human or handled.
Hidden by default
List and thread queries leave out messages labelled SPAM, BLOCKED, TRASH, QUARANTINE or UNAUTHENTICATED. Ask for them explicitly with include_spam, include_blocked, include_trash, include_quarantine or include_unauthenticated set to true, or by filtering on the label itself (labels=QUARANTINE). Spam, blocked, unauthenticated and trash also need the matching label_*_read permission on restricted keys.
Read state and other label changes
The response is the updated message. To change many at once, POST /v0/inboxes/{inbox_id}/messages/batch-update takes message_ids (up to 100), add_labels and remove_labels.
Listing and filtering messages
GET /v0/inboxes/{inbox_id}/messages and the thread list endpoints accept:
| Parameter | Notes |
|---|---|
limit, page_token | Default 50, maximum 500. |
ascending | true for oldest first. Default is newest first. |
labels | Only messages with any of these labels. Repeat the parameter or use commas. |
before, after | ISO 8601 timestamps. |
include_spam, include_blocked, include_trash, include_quarantine, include_unauthenticated | Include hidden messages. |
q | Thread lists only: full-text filter. |
POST /v0/inboxes/{inbox_id}/messages/batch-get with { "message_ids": [...] } returns up to 100 full messages in one call. Search is covered in Search and export.
Attachments
Each message lists its attachments with attachment_id, filename, size, content_type, content_disposition and content_id. Inbound attachments are also risk-scored in trust.attachments.
GET /v0/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}returns the metadata plusdownload_urlandexpires_at. Depending on storage,download_urlis a short-lived signed URL or an API URL that needs your Bearer key.GET .../attachments/{attachment_id}/downloadreturns the bytes directly.
See the attachments guide.
Raw MIME
GET /v0/inboxes/{inbox_id}/messages/{message_id}/raw returns the original message as message/rfc822. Use it for archiving or your own parsing, not as model input.
Deleting
DELETE /v0/inboxes/{inbox_id}/messages/{message_id} removes the message, its raw MIME and attachments, and rebuilds the thread. A thread with no messages left is removed.