Receiving Mail

An inbound route matches a recipient and POSTs to a URL you control. Every message is SPF, DKIM, DMARC and ARC verified, virus-scanned and spam-scored before your webhook fires, and the verdicts travel with the payload.

Routes

curl -X POST "$API/v1/tenants/$TENANT_ID/inbound-routes" \
  -H "Authorization: Bearer $ADMIN_KEY" -H 'Content-Type: application/json' \
  -d '{
    "match_type": "domain",
    "pattern": "agents.example.com",
    "webhook_url": "https://your-app.example.com/hooks/inbound",
    "llm_classify": true,
    "priority": 100
  }'
match_typeMatches
exactOne full address, compared case-insensitively
domainEvery recipient on that domain
regexThe recipient against your pattern; an invalid pattern is skipped rather than fatal
catch_allAnything not already matched

Routes are evaluated in priority order and the first match wins. llm_classify: true asks Sentio to classify borderline mail, which earns its cost on a catch-all route where anything can arrive.

Inbound routes are administered with an admin-scoped key, while event webhooks are scoped to the calling tenant. They are two different streams with two different credentials, and treating them as one is the most common early mistake. Plan for both from the start.

What Arrives At Your Webhook

The POST body is the inbound payload, not the message itself. It carries identity, the envelope, the verdicts, the threading headers, and a key you use to fetch the raw message.

FieldWhat it is
message_id, tenant_id, domain_idIdentity. message_id is what you use against the API.
envelope_from, envelope_toThe SMTP envelope, which is not always the From: and To: headers.
raw_eml_keyWhere the stored message lives. See below.
spam_score, spam_actionThe content-tier verdict.
llm_category, llm_summaryPresent when the route asked for classification and the score fell in the review band.
in_reply_to, referencesThreading, as bare Message-IDs with no angle brackets. This is what lets you attach the mail to an existing conversation.
auto_submitted, list_id, precedenceLoop guards, raw. Your intake gate decides what counts as bulk or automated.
dsn_ret, dsn_envid, dsn_notify, dsn_orcptDelivery status notification parameters, when the sender supplied them.
queued_atWhen it entered the queue.

The body and attachments are not in the payload. The webhook tells you a message arrived and how it scored; fetch the content from the API when you want it, with GET /v1/messages/{id}/raw for the original EML and GET /v1/messages/{id}/attachments for the parts. That keeps the webhook small and predictable no matter what someone mailed you.

Use The Verdicts As A Gate

The authentication and scanning results exist so your handler can decide before a model ever sees the text. Drop or quarantine junk in the handler. An agent should never be asked to reason about whether a sender was forged, and every token spent on obvious spam is wasted twice, once on cost and once on the risk that the model believes it.

The message body is untrusted input from anyone in the world. Agent Inboxes covers what follows from that.

Answer Fast, Work Later

Acknowledge with a 2xx as soon as the message is durably enqueued on your side, then do the work asynchronously. Sentio classifies your response and retries accordingly:

Your responseWhat Sentio does
2xxSuccess. Delivery recorded.
5xx, 408, 429Transient failure. Retried on a later delivery attempt.
Other 4xxPermanent failure. Not retried.
Network error, timeout, DNS failureTransient failure. Retried.

Retries are scheduled through the queue rather than slept in-process, so they survive a Sentio restart and do not tie up a consumer. A slow handler turns into duplicate deliveries, so make your endpoint idempotent on message_id.

Every attempt is written to a delivery log, so a route that has started failing is visible rather than something you infer from missing mail.