Agent Inboxes

A mailbox is an address that belongs to a tenant's domain. Give each agent one, and mail addressed to it arrives at your webhook already authenticated, scanned and scored. This page follows the repository's agent guide, whose ordering is the order in which mistakes get expensive.

Addressing

Give each agent a stable address on a domain you control, <agent-slug>@agents.example.com. Three rules are worth adopting before you have users, because each is painful to retrofit.

RuleWhy
Globally unique, not unique per tenantTwo tenants that both want support@ collide the moment they share a domain.
Append-only, never reusedReplies to a months-old thread still arrive. Handing a retired address to a different tenant leaks one customer's mail to another.
Allocate at creation, not on first sendThe address should exist before anyone is told about it.

Three Calls

KEY="sentio_bootstrap_admin_CHANGE_ME"
API="http://localhost:8080"
TENANT="00000000-0000-0000-0000-000000000001"

# 1. A receiving domain for this tenant
DOMAIN_ID=$(curl -s -X POST "$API/v1/domains" \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"domain_name":"acme.example.com","use_for_receiving":true,"use_for_sending":true}' \
  | jq -r .data.id)

# 2. One mailbox per agent. metadata is yours - put the agent id in it.
curl -X POST "$API/v1/domains/$DOMAIN_ID/mailboxes" \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{
    "address": "support-agent",
    "display_name": "Acme Support Agent",
    "metadata": {"agent_id": "agt_01H8..."}
  }'

# 3. Route the domain to your application
curl -X POST "$API/v1/tenants/$TENANT/inbound-routes" \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{
    "match_type": "domain",
    "pattern": "acme.example.com",
    "webhook_url": "https://your-platform.example.com/hooks/inbound",
    "priority": 100
  }'

support-agent@acme.example.com is now live.

One match_type: "domain" route across the whole domain is usually better than a route per agent: one route to keep healthy instead of thousands, and a new agent works the moment its slug is allocated. Resolve the slug to an agent in your own handler.

Mailbox Options

FieldEffect
metadataFree-form JSON. The natural place for your own agent_id.
forward_toForward inbound mail on to external addresses. See escalation below.
auto_replyImmediate acknowledgement, threaded via In-Reply-To, while the agent works.
statusdisabled stops delivery without deleting history.

Replying In Thread

Pass the inbound Message-ID back as in_reply_to and the reply files into the existing conversation rather than starting a new one. Build references by taking the parent's References and appending the parent's Message-ID.

curl -X POST "$API/v1/messages/send" \
  -H "Authorization: Bearer $TENANT_KEY" -H 'Content-Type: application/json' \
  -d '{
    "from": "support-agent@acme.example.com",
    "to": ["customer@example.net"],
    "subject": "Re: Order #1234",
    "text": "Refund processed - you should see it in 3-5 days.",
    "in_reply_to": "<abc123@example.net>",
    "references": ["<abc123@example.net>"]
  }'

The reply is DKIM-signed with the tenant's own key, so it authenticates as their domain rather than yours.

Treat The Body As Hostile

This is the part with no equivalent in an ordinary webhook integration: anyone in the world can send text to your agent, and that text ends up in a model's context.

  • Keep the body as data, never as instructions. Do not concatenate it into a system prompt.
  • Bound the blast radius. An agent acting on mail should not hold credentials or tools whose misuse you could not tolerate a stranger triggering.
  • Put a human in the loop for irreversible actions - refunds, account changes, anything outward-facing.
  • Constrain who an agent may reply to. Replying only to the inbound sender stops a crafted message turning your agent into a relay.
  • Rate-limit per sender and per tenant, so one adversary cannot exhaust a tenant's budget.

Sentio's verdicts arrive with the payload for exactly this reason: use them as an intake gate before you spend a token. Junk should be dropped by your handler, not reasoned about by a model.

Escalating To A Human

An agent that cannot answer should hand off rather than guess. Set forward_to and everything arriving at that mailbox is forwarded on, to any address anywhere.

curl -X PUT "$API/v1/domains/$DOMAIN_ID/mailboxes/$MAILBOX_ID" \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{
    "address": "support-agent",
    "forward_to": ["oncall@gmail.com", "team@helpdesk.example.net"]
  }'

Forwarding is where most mail servers quietly break DMARC. They relay the message unchanged, so the original From: no longer aligns with the forwarding host's SPF and the receiver rejects it. Sentio rewrites the envelope instead: From: becomes the mailbox and the message is re-signed with that domain's DKIM key, so it authenticates as yours and survives the trip. The original sender is preserved in Reply-To: and Resent-From:, so hitting reply still answers the person who wrote in, and Resent-To: and Resent-Date: record the hop per RFC 5322. The body is untouched.

Give the forwarding domain its own DKIM key. Re-signing is what makes the rewritten From: authenticate. Without an active key the forward still goes out, but unsigned, and picky receivers will treat it accordingly.

This is also the safest way to start: point a new agent's mailbox at a human inbox, watch what actually arrives for a week, and only then let the agent answer on its own. Pair it with auto_reply to acknowledge the sender immediately while the mail is on its way to a person.

Many Tenants On One Deployment

Create a tenant per customer, then give each its own domains, mailboxes and API key.

curl -X POST "$API/v1/tenants" -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Acme Corp","tier":"shared_premium"}'

curl -X POST "$API/v1/tenants/$NEW_TENANT_ID/api-keys" -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Acme production","scopes":["*"]}'

Tiers (dedicated, shared_premium, shared_standard) select the isolation level, including whether the tenant sends from a dedicated IP pool. Rate limits, suppression lists and Bayesian spam profiles are all per tenant, so one noisy customer cannot spend another's reputation.

The checklist from the agent guide: one tenant per customer, a per-tenant API key rather than the bootstrap key, their own sending domain and DKIM key, and a warmup schedule rather than full volume on day one for anyone whose volume justifies a dedicated pool.