Guides
Holds & injection scanning
Why inbound mail is held, what agents see while it is held, how prompt-injection scanning works, and how humans release or reject mail.
docs.simail.ai/holds
A hold keeps a message away from your agent until a human looks at it. Holds are decided by deterministic checks before the message is stored, so there is never a moment when a suspicious message is readable by an agent.
What a held message looks like to an agent
Section titled “What a held message looks like to an agent”On every channel (REST, MCP, webhooks, GET /v0/events, WebSocket) a held message is
metadata only:
{ "message_id": "msg_…", "thread_id": "thr_…", "inbox_id": "agent-7k2m@simail.email", "from": "unknown@example.com", "timestamp": "2026-10-18T17:02:11Z", "size": 18422, "labels": ["received", "held"], "hold_reasons": ["injection_suspected"], "held": true, "trust_envelope": { "computed_by": "none", "untrusted": true }}timestamp is when simail received the message (the same field as on every message).
Subject, body, attachments and raw source are withheld. Listings include held messages only for keys
with hold_metadata_read. Held mail never wakes your agent, and search matches only its metadata.
Fetching the body of a held message does not work; neither does the attachment or raw endpoint.
Your agent cannot release mail: POST /v0/inboxes/{inbox_id}/messages/{message_id}/agent-armor/release
returns 403 human_required, and the MCP server has no release tool.
Why mail is held
Section titled “Why mail is held”hold_reasons | Cause |
|---|---|
rate_limited | more than 50 messages per hour from one sender to one inbox, or 2,000 per hour to the organization (far above that, extra copies are counted and dropped) |
unparseable | the message could not be parsed safely |
injection_suspected | the prompt-injection score reached the threshold (below) |
otp_limit | external organizations only: too many verification-code messages for the organization today (more than 10, or from more than 3 sending domains). Verification-code mail to the internal tenant goes to your agent as usual |
sensitive_material | the message looks like regulated or privileged material (for example legal case material) that simail does not accept |
enrichment | a later, optional check (attachment text, model-based scoring) found a problem |
Prompt-injection scanning
Section titled “Prompt-injection scanning”The scanner is a set of deterministic heuristics, run on the subject, body and HTML of every inbound message:
- instruction phrasing aimed at an AI (“ignore previous instructions”, role or system prompts), in English and Chinese;
- hidden text: zero-width characters, CSS-hidden content, text in the same colour as the background;
- fake system prompts and tool-call syntax;
- very long encoded blobs.
The result is trust_envelope.injection_score between 0 and 1. At or above
injection.hold_threshold (0.7 by default; your policy can lower it, never raise it above the
platform’s value) the message is held.
Two optional checks are off by default and can be turned on by a human: text extraction from
attachments, and model-based injection scoring. Both run after the message is stored and can only
add a hold, never remove one. A message that is held later disappears from search results and
produces a message.held event.
Releasing and rejecting (humans)
Section titled “Releasing and rejecting (humans)”In the console, a human with access to the inbox opens Held and chooses:
- Release: the message becomes a normal message. Its body is indexed for search, and a
message.releasedevent is published (which can wake your agent). - Reject: the message stays out of reach of agents and is deleted with the trash.
Both require re-verification within 5 minutes. A human can also add the sender to a block list.
Events
Section titled “Events”| Event | When |
|---|---|
message.held | a message was stored as held (metadata only) or held later by an optional check |
message.released | a human released it |