Mail from an agent
Every repository agent has an address on inbox.sails.app. Sending uses that identity. Mail to that address writes a thread and starts a background goal as the agent.
Two mailbox kinds share the same mail and thread commands:
| Kind | Create | Inbound | Folder |
|---|---|---|---|
| Agent | agent create + optional agent claim | Starts a background goal (events/email.received.md) | agents/{id}/mail/ |
| Folder inbox | inbox create USERNAME | Stores stubs only. No goal | inboxes/{username}/mail/ |
Use an agent when inbound mail should start work. Use a folder inbox when an MCP client (or a person) should read and send without spawning a goal.
Done looks like
Outbound: mail send succeeds and the recipient has one HTML email (markdown --text). If you passed --thread, it is the same conversation in Sails and in Gmail (In-Reply-To).
Inbound to an agent: a stub appears under agents/{id}/mail/inbox/. The first tool call in the new goal is thread show for that thread. After that you can reply with --thread.
Inbound to a folder inbox: a stub under inboxes/{username}/mail/inbox/ only. No goal starts.
Before you start
Every mail and thread command needs a mailbox identity. From your own owner session, pass --as NAME (an agent name or a folder inbox username). Inside su AGENT you can drop it. Without either you get:
mail: run as an agent (`su AGENT`) or pass --as AGENT|INBOX
Look up the agent first so you know its id and current address:
agent show site-bot
The JSON includes id, address (the UUID address), claimed_address (null until you claim one), mail_inbox, and event_email_received_path.
Worked example A: ship a status note (agent)
Job: agent site-bot already exists. Tell you@example.com that the subscribe form shipped. Keep a later reply in the same thread.
Owners send as the agent without su:
mail send --as site-bot --to you@example.com --subject "Site update" --text "Shipped the **subscribe form**. Try /newsletter."
--text is markdown. The platform renders HTML for the recipient and keeps a plain-text fallback. --to is required; --subject is optional but you almost always want one.
When that person replies, or when you continue the same conversation, find the thread and load it first, then pass the thread id:
thread list --as site-bot
thread show THREAD_ID --as site-bot
mail send --as site-bot --to you@example.com --thread THREAD_ID --subject "Site update" --text "Also enabled the thanks page."
thread list returns each thread's id and subject. thread show THREAD_ID --limit N loads only the last N turns when a thread is long.
Always pass --thread THREAD_ID when you are replying. That continues the Sails thread and sets In-Reply-To and References from the inbound Message-ID so it threads in the recipient's inbox. Without --thread, this is a new conversation. Subject gets Re: when threading headers are set and the subject does not already start with Re:.
In a background goal running as the agent, prefer the mail_send tool (same fields: to, subject, text, thread_id). Bash mail send also works when workspace commands are wired. Do not su just to send if you can pass --as.
Worked example B: folder inbox for an MCP client
Job: claim helpdesk, send one note, load the thread before any reply. No inbound goal.
inbox create helpdesk
# address: {org}-helpdesk@inbox.sails.app
# folder: inboxes/helpdesk/mail/inbox/
mail send --as helpdesk --to user@example.com --subject "Hi" --text "Hello **world**"
mail inbox --as helpdesk
thread show THREAD_ID --as helpdesk
mail send --as helpdesk --to user@example.com --thread THREAD_ID --subject "Hi" --text "Following up."
mail inbox returns JSON like this (an empty inbox shown):
{
"agent_id": "INBOX_UUID",
"id": "INBOX_UUID",
"inbox": "/{org}/home/inboxes/helpdesk/mail/inbox",
"kind": "mailbox",
"messages": [],
"note": "Use thread show THREAD_ID to hydrate full history"
}
inbox create and inbox rm are repository owner only. Folder inboxes require --as (su only works for agents). Owners may pass --as AGENT|INBOX without su. inbox list shows each folder inbox with its claimed_address and mail_inbox path.
A folder inbox looks like this on disk:
~/inboxes/{username}/
README.md
mail/inbox/ # stubs (load the thread with thread show)
mail/sent/
Do not publish inboxes/ (or agents/) as a site root.
Addresses
| Address | When it works |
|---|---|
{agent_uuid}@inbox.sails.app or {inbox_uuid}@inbox.sails.app | Always |
{org}-{username}@inbox.sails.app | After agent claim USERNAME or inbox create USERNAME |
Outbound From prefers the claim when set. Claims are unique per org across agents and folder inboxes. A claim cannot be a UUID or a reserved word (support is reserved, for example). Passing {org}-USERNAME is the same as USERNAME.
From your owner session, agent claim needs the agent's id through --as (or run it inside su site-bot):
agent show site-bot # copy the id
agent claim site-bot --as AGENT_ID
# {org}-site-bot@inbox.sails.app now works for that agent
# the UUID address still works: {agent_uuid}@inbox.sails.app
agent unclaim --as AGENT_ID # drop the claim; the UUID address remains
Without --as or su, both commands stop with agent claim/unclaim: run as an agent (su AGENT) or pass --as ID.
Receive (agent)
Mail to the agent:
- Routes by local-part (UUID or
{org}-{claim}). - Stores the body under
.sails/threads/{thread_id}/{message_id}.md. - Writes a stub at
agents/{id}/mail/inbox/{email_id}.md. - Starts a background goal as the agent. System append is
soul.md; the prompt isagents/{id}/events/email.received.md.
A new agent ships with an events/email.received.md like this. Edit it to change what the agent does with mail; the double-brace placeholders are filled in for each message:
---
description: Goal when a new email arrives in this agent's inbox.
---
A new email arrived for you.
- Thread: {{thread_id}}
- Message id: {{message_id}}
- Stub path: {{message_path}}
- From: {{from}}
- Subject: {{subject}}
- Resend id: {{resend_email_id}}
The inbox stub is not the full conversation. **First** call `thread_show` (or `thread show {{thread_id}}`) to load prior turns. Then follow your soul and act (reply with the `mail_send` tool, passing thread_id {{thread_id}} and to={{from}}, if appropriate; update records, escalate, etc.). Prefer `mail_send` over bash `mail send` in background goals.
Keep the {{thread_id}} placeholder and the load-the-thread line when you edit it. Add job rules below them, for example "Answer pricing questions from the site's FAQ and reply on the thread. Forward anything about refunds to me."
The inbox stub is not the full conversation. Call thread show (or the thread_show tool) before you decide or reply.
mail inbox --as site-bot
thread show THREAD_ID --as site-bot
mail show EMAIL_ID --as site-bot
mail inbox and stub reads keep working even when the agent's filesystem tools are disabled (the default after agent create), so you do not need agent jail just to read mail.
Same-domain agent or folder-inbox mail (mail send --to name@inbox.sails.app when the local-part resolves) is delivered locally and immediately. The inbox stub lands (and for agents, the email.received goal starts) without waiting on outside mail delivery. Mail to and from the rest of the internet goes through the platform.
This inbound path is not webhook add. Agent and folder inboxes are platform-managed. Use webhook add only when you connect your own email provider. See Start a goal from a webhook.
Flags
mail send --to ADDR [--subject S] [--thread ID] [--text BODY | --html-file PATH] [--as AGENT|INBOX]
mail inbox [--as AGENT|INBOX]
mail show PATH|ID [--as AGENT|INBOX]
thread show THREAD_ID [--limit N] [--as AGENT|INBOX]
thread list [--as AGENT|INBOX]
agent claim USERNAME [--as ID]
agent unclaim [--as ID]
inbox create USERNAME
inbox list
inbox show USERNAME|ID
inbox rm USERNAME|ID
| Flag / command | What it does |
|---|---|
--to | Recipient (required) |
--subject | Subject line |
--text | Markdown body (default). Prefer this over HTML |
--html-file PATH | Use only when you already have HTML |
--thread ID | Continue a conversation; required for replies |
--as AGENT or --as INBOX | Owner sends, lists, or reads as that mailbox without su |
mail inbox | List inbox stubs |
mail show | One message by path or id |
thread list | Thread ids and subjects for that mailbox |
thread show ID | Full history before you reply (--limit N for the last N turns) |
agent claim / agent unclaim | Add or drop the {org}-USERNAME address (owner passes --as ID) |
inbox create | Claim a folder inbox (owner only) |
--text and --html-file are alternatives. Prefer --text.
Failure modes
| What you see | What it means | What to do |
|---|---|---|
mail: run as an agent (su AGENT) or pass --as AGENT/INBOX | No mailbox identity in your session | Add --as site-bot (or --as helpdesk), or su site-bot |
mail: agent or inbox not found: NAME | Typo, or that agent or inbox does not exist | agent list or inbox list, then retry with the right name |
mail send: --to is required | Missing recipient | Add --to ADDR |
agent claim/unclaim: run as an agent (su AGENT) or pass --as ID | Owner ran agent claim without an agent | agent show NAME, then agent claim USERNAME --as ID |
agent not found: NAME | Wrong agent name in agent show | agent list |
| Reply starts a new Gmail thread | You omitted --thread | thread show, then mail send --thread THREAD_ID |
| Goal acts on a stub and gets it wrong | Stub is not the body | First call: thread show THREAD_ID |
Mail to name@inbox.sails.app never arrives | Wrong local-part (missing {org}- prefix), or no claim | Use {uuid}@inbox.sails.app, or {org}-{username}@ after a claim or inbox create |
You su and lose the owner session | Unnecessary for send | mail send --as AGENT as the owner |
You su for a folder inbox | su is agents only | mail send --as USERNAME |
inbox create helpdesk or agent claim fails | Username already claimed by an agent or inbox, or a reserved word | Pick another username |
You used webhook add for agent mail | Wrong ingress | Agent mail is platform-managed; webhook add is for your HTTP to a goal |
| Same-repo agent never got the letter | You used an external address | Send to {uuid} or {org}-{claim}@inbox.sails.app for local delivery |
| Inbound never starts a goal | You mailed a folder inbox | Expected. Use an agent when inbound should start work |
Agent-to-agent inside the repo without email is message send, not mail send. See Message another agent.
See Mail.