These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

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:

KindCreateInboundFolder
Agentagent create + optional agent claimStarts a background goal (events/email.received.md)agents/{id}/mail/
Folder inboxinbox create USERNAMEStores stubs only. No goalinboxes/{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

AddressWhen it works
{agent_uuid}@inbox.sails.app or {inbox_uuid}@inbox.sails.appAlways
{org}-{username}@inbox.sails.appAfter 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:

  1. Routes by local-part (UUID or {org}-{claim}).
  2. Stores the body under .sails/threads/{thread_id}/{message_id}.md.
  3. Writes a stub at agents/{id}/mail/inbox/{email_id}.md.
  4. Starts a background goal as the agent. System append is soul.md; the prompt is agents/{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 / commandWhat it does
--toRecipient (required)
--subjectSubject line
--textMarkdown body (default). Prefer this over HTML
--html-file PATHUse only when you already have HTML
--thread IDContinue a conversation; required for replies
--as AGENT or --as INBOXOwner sends, lists, or reads as that mailbox without su
mail inboxList inbox stubs
mail showOne message by path or id
thread listThread ids and subjects for that mailbox
thread show IDFull history before you reply (--limit N for the last N turns)
agent claim / agent unclaimAdd or drop the {org}-USERNAME address (owner passes --as ID)
inbox createClaim a folder inbox (owner only)

--text and --html-file are alternatives. Prefer --text.

Failure modes

What you seeWhat it meansWhat to do
mail: run as an agent (su AGENT) or pass --as AGENT/INBOXNo mailbox identity in your sessionAdd --as site-bot (or --as helpdesk), or su site-bot
mail: agent or inbox not found: NAMETypo, or that agent or inbox does not existagent list or inbox list, then retry with the right name
mail send: --to is requiredMissing recipientAdd --to ADDR
agent claim/unclaim: run as an agent (su AGENT) or pass --as IDOwner ran agent claim without an agentagent show NAME, then agent claim USERNAME --as ID
agent not found: NAMEWrong agent name in agent showagent list
Reply starts a new Gmail threadYou omitted --threadthread show, then mail send --thread THREAD_ID
Goal acts on a stub and gets it wrongStub is not the bodyFirst call: thread show THREAD_ID
Mail to name@inbox.sails.app never arrivesWrong local-part (missing {org}- prefix), or no claimUse {uuid}@inbox.sails.app, or {org}-{username}@ after a claim or inbox create
You su and lose the owner sessionUnnecessary for sendmail send --as AGENT as the owner
You su for a folder inboxsu is agents onlymail send --as USERNAME
inbox create helpdesk or agent claim failsUsername already claimed by an agent or inbox, or a reserved wordPick another username
You used webhook add for agent mailWrong ingressAgent mail is platform-managed; webhook add is for your HTTP to a goal
Same-repo agent never got the letterYou used an external addressSend to {uuid} or {org}-{claim}@inbox.sails.app for local delivery
Inbound never starts a goalYou mailed a folder inboxExpected. 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.