Receiving
The inbound pipeline
Section titled “The inbound pipeline”Mail to your address is accepted or permanently rejected at SMTP time. Accepted mail goes through deterministic checks before it is stored, so there is never a moment when a message is readable but not yet checked. No language model runs on the receive path.
- Size (up to 10 MiB), recipient address, inbox state and your receive block list. Unknown, retired, paused or unclaimed inboxes and blocked senders are rejected permanently.
- Per-sender rate limits (50 messages per hour per sender per inbox; 2,000 per hour per organization). Mail over the limit is accepted and held, not bounced.
- The raw message is stored first, then parsed.
- Duplicates (same Message-ID and same content) are dropped. Same Message-ID with different content
is kept and flagged
msgid_collision. - Threading (see Threads).
- Classification:
normal,spam,auto_reply(RFC 3834 headers, vacation replies, and loop markers on mail sent by SiMail agents),bounce(delivery and feedback reports),otp(verification-code mail) andsystem. - Prompt-injection heuristics (instruction phrasing, hidden or zero-width text, CSS-hidden content,
fake system prompts, long encoded blobs) produce an
injection_score; at or above the threshold (0.7 by default; your policy can make it stricter) the message is held. - A screen for regulated or privileged material holds matching messages for a human.
- Reply extraction produces
extracted_text: the new part of a reply without quoted history (including common Chinese quote markers). - Everything is committed at once, then events are published.
Optional, off by default: attachment text extraction and model-based injection scoring run after storage and can only add a hold, never release one.
Message object
Section titled “Message object”{ "message_id": "msg_…", "thread_id": "thr_…", "labels": ["received", "unread"], "timestamp": "2026-10-18T17:02:11Z", "from": "Ada <ada@example.com>", "to": ["shop-helper@simail.email"], "cc": [], "subject": "Where is my order?", "preview": "Hi, I ordered last week and…", "text": "Hi, I ordered last week and…", "extracted_text": "Hi, I ordered last week and haven't heard back.", "attachments": [{ "attachment_id": "att_…", "filename": "receipt.pdf", "size": 48213, "content_type": "application/pdf" }], "in_reply_to": null, "references": [], "size": 61840, "created_at": "2026-10-18T17:02:12Z", "trust_envelope": { "auth": { "spf": null, "dkim": null, "dmarc": null, "computed_by": "none" }, "sender_known": false, "first_contact": true, "classification": "normal", "hold": false, "hold_reasons": [], "injection_score": 0.05, "flags": [], "untrusted": true }}htmlis returned only to keys withmessage_raw_read(and to humans), and SiMail never renders it for you. Preferextracted_textandtext.trust_envelope.auth.computed_byis"none"for now: SiMail does not yet verify DKIM itself, so do not treat the From address as authenticated.sender_knownis true when the sender is a contact of this inbox (someone you wrote to first, or someone a human promoted). Mail from other SiMail customers is treated like any stranger.Reply-Tois stored but never used to decide where replies go.
Held messages
Section titled “Held messages”A held message (hold: true, label held) shows metadata only on every channel (REST, MCP,
webhooks, events, WebSocket): message_id, thread_id, sender address, received time, size, labels
and hold_reasons. Subject, body and attachments are withheld. Only a human can release or reject
it in the console. POST …/messages/{message_id}/agent-armor/release returns 403 human_required.
hold_reasons explains why, for example rate_limited or unparseable, or a hold from the injection,
one-time-code or sensitive-material checks.
Listing and reading
Section titled “Listing and reading”| Method and path | Permission |
|---|---|
GET /v0/inboxes/{inbox_id}/messages |
message_read |
GET /v0/inboxes/{inbox_id}/messages/{message_id} |
message_read |
PATCH /v0/inboxes/{inbox_id}/messages/{message_id} (labels) |
message_update |
DELETE /v0/inboxes/{inbox_id}/messages/{message_id} (to trash; purged after 30 days) |
message_delete |
POST /v0/inboxes/{inbox_id}/messages/batch-get, batch-update |
message_read, message_update |
GET /v0/inboxes/{inbox_id}/messages/search?q= |
message_read |
GET /v0/inboxes/{inbox_id}/messages/{message_id}/raw |
message_raw_read |
GET /v0/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id} |
message_read |
Query parameters: labels, before, after, include_spam, include_blocked, include_trash
(each needs the matching label_*_read permission), limit, page_token, ascending.
Search uses full-text matching on subject, sender and body and works for English and Chinese. Held messages are searchable by metadata only.
Attachments and raw
Section titled “Attachments and raw”Attachment and raw responses do not contain the file. They return a short-lived, single-use download link on the isolated content domain:
{ "attachment_id": "att_…", "filename": "receipt.pdf", "size": 48213, "content_type": "application/pdf", "download_url": "https://view.siagentmail.com/…", "expires_at": "2026-10-18T17:07:12Z" }Links expire after 5 minutes and work once. Files are always served as downloads
(Content-Disposition: attachment), never rendered.
Which events wake your agent
Section titled “Which events wake your agent”Each message.received event carries data.wake. Mail from known threads, contacts or your
receive allow list wakes your agent. Mail from strangers wakes it only up to a daily budget per inbox
and an hourly budget per sender; beyond that, events are still recorded and available from
GET /v0/events, but webhooks and WebSocket skip them unless you subscribe with
include_non_wake: true. A daily inbox.digest event reports the counts. Held mail never wakes.