Docs
Messages and threads
How inbound mail is parsed, threaded, labeled and screened before your agent reads it.
Inbound mail is parsed into messages and grouped into threads using the In-Reply-To and References headers. Your agent reads conversations, not raw MIME.
Threads
List threads of one inbox, or across the whole organization with GET /v0/threads. Lists are newest first and paginated with limit (1 to 100, default 20) and page_token (from next_page_token). GET /v0/threads/{thread} returns the thread with all messages, oldest first.
Messages
Listing returns metadata without bodies, to keep responses small for agents. Getting one message returns:
textandhtml: the bodies as sent.extracted_text: only the new text, with quoted history and mobile signatures removed. Set by the gatekeeper; prefer it when you pass a message to a model.authentication_results: DKIM result for the From domain, spam score and spam action.gatekeeper: prompt injection screening, see below.attachments: id, filename, content type and size. See Attachments.
The original message is available as RFC 822 from GET .../messages/{message}/raw. The {message} path parameter accepts the message UUID or the RFC Message-ID (URL encoded).
Gatekeeper verdict
Every inbound message is screened for prompt injection before it is marked complete. The verdict is clear or review, with a score and the reasons that triggered it, such as instruction_override, data_exfiltration, role_hijack, fake_tool_markup, hidden_html_text, invisible_unicode or dmarc_fail.
"gatekeeper": {
"status": "complete",
"verdict": "review",
"score": 0.8,
"reasons": ["instruction_override", "data_exfiltration"],
"model": "gatekeeper-heuristics-v1"
}status is pending until screening finishes, unavailable if the screener could not run and timeout if no verdict arrived within 15 minutes. Messages with verdict review also get the review label, and the message.security.review event fires.
Screen before notify
Every organization has the setting screen_before_notify, on by default (see GET /v0/auth/me). Inbound mail is held until the gatekeeper verdict exists, so an agent never reads a prompt injection before screening finishes. The message field screening shows the hold state:
| `screening` | Meaning | Visible | Events |
|---|---|---|---|
| (pending) | Waiting for the gatekeeper. | Never returned. | None yet. |
allowed | Verdict clear. | Yes. | message.security.completed, then message.received. |
review | Verdict review, screener unavailable or timed out: the hold fails closed. | Only with include_review=true and message_review. | message.security.review only, without bodies. |
released | A reviewer released it. | Yes. | message.received. |
Held messages are left out of message and thread lists, gets, raw downloads and attachments, and cannot be replied to or relabeled. A thread started by a held message stays hidden until one of its messages is visible. The same rules apply to the MCP tools.
# a key with message_read and message_review
curl -s "https://api.mueslimail.com/v0/inboxes/billing@agents.your-domain.eu/messages?include_review=true&label=review" -H "Authorization: Bearer $MAI_REVIEW_KEY"
# a key with message_release: the message becomes visible and message.received fires
curl -s -X POST "https://api.mueslimail.com/v0/inboxes/billing@agents.your-domain.eu/messages/$MESSAGE_ID/release" -H "Authorization: Bearer $MAI_REVIEW_KEY"Labels
The platform sets system labels: received and unread on inbound mail, sent on outbound mail, spam when the spam filter flags a message, review when the gatekeeper does. Add your own labels to track workflow state, and filter any list with ?label=.
curl -s -X PATCH "https://api.mueslimail.com/v0/inboxes/billing@agents.your-domain.eu/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $MAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"add_labels": ["invoice", "approved"], "remove_labels": ["unread"]}'Endpoints
GET/v0/inboxes/{inbox}/threads
List threads of an inbox
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
label | string | in query |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: ThreadList
GET/v0/threads
List threads across the organization
Parameters
| Name | Type | Notes |
|---|---|---|
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
label | string | in query |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: ThreadList
GET/v0/threads/{thread}
Get a thread with the messages the caller may see, oldest first
Parameters
| Name | Type | Notes |
|---|---|---|
thread | string (uuid) | in path, required |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: Thread
GET/v0/inboxes/{inbox}/messages
List message metadata (no bodies)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
limit | integer | in query, min 1, max 100, default 20 |
page_token | string | in query |
label | string | in query |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: MessageList
GET/v0/inboxes/{inbox}/messages/{message}
Get a message with bodies, extracted_text, authentication results and gatekeeper verdict
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: Message403Error envelope: Error404Error envelope: Error
PATCH/v0/inboxes/{inbox}/messages/{message}
Add or remove custom labels
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Body
| Name | Type | Notes |
|---|---|---|
add_labels | string[] | |
remove_labels | string[] |
Responses
200OK: Message
GET/v0/inboxes/{inbox}/messages/{message}/raw
Raw RFC 822 message
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
include_review | boolean | in query, Also return messages held for security review; needs message_review, default false |
Responses
200OK: message/rfc822
POST/v0/inboxes/{inbox}/messages/{message}/release
Release a message held for review (needs message_release, audited)
Parameters
| Name | Type | Notes |
|---|---|---|
inbox | string (email) | in path, required |
message | string | in path, required, Message UUID or RFC Message-ID (URL encoded) |
Responses
200Released: Message403Error envelope: Error404Error envelope: Error409Error envelope: Error
Thread object
| Name | Type | Notes |
|---|---|---|
thread_id | string (uuid) | |
inbox_id | string | |
subject | string | |
preview | string | |
labels | string[] | |
message_count | integer | |
timestamp | string (date-time) | |
messages | Message[] | |
retention_purged_at | string (date-time) | Set when retention deleted the last message of the thread. A thread with no messages answers 200 with this and retention_notice, instead of 404, because a ticket may point at it. |
retention_notice | string | The sentence in place of a body the retention window removed. |
Message object
| Name | Type | Notes |
|---|---|---|
id | string (uuid) | |
message_id | string | RFC 5322 Message-ID with angle brackets |
thread_id | string (uuid) | |
inbox_id | string | |
direction | in | out | |
from | string | |
to | string[] | |
cc | string[] | |
subject | string | |
text | string | |
html | string | |
extracted_text | string | New text without quoted history (set by the gatekeeper) |
preview | string | |
snippet | string | Excerpt around the match. Only in search results, and empty for a key holding message_metadata without message_read |
labels | string[] | |
in_reply_to | string | |
references | string[] | |
status | received | queued | sending | sent | failed | held | cancelled | |
delivery_error_code | unauthorized_ip | auth_failed | relay_unreachable | connection_refused | tls_failed | unknown | Why an outbound message did not leave. Set when a delivery attempt was refused, absent otherwise; with status failed, the message is not delivered and the console says so. |
delivery_error | string | What the mail server said when it refused the message, one line, at most 500 characters, never a credential. |
last_error | string | The last reason a message did not leave, from the transport or from the platform: a send held by undo send whose window closed while sending was paused fails with the reason of the pause here, and no delivery_error, because no mail server refused it. |
cancel_window_ends_at | string (date-time) | End of the undo window of a send born held. Before it, POST .../cancel withdraws the message; after it, the message is on its way and cancel answers 409 invalid_state. Absent on a message that never had a window. |
authentication_results | object | What the SMTP edge verified for inbound mail: dkim, spf, dmarc, dmarc_policy, antivirus, spam_score, spam_action. Mail failing DMARC is labelled dmarc-fail, and spam under p=quarantine or p=reject. |
gatekeeper | object | |
screening | review | allowed | released | Hold state under screen before notify; absent when the message was not held. Pending messages are never returned. |
attachments | object[] | |
created_at | string (date-time) |