# AgentMail

Canonical: https://auggy.dev/docs/augment-agentmail
Status: Published
Package: auggy 0.5.0

Give an agent a policy-gated inbox with durable inbound delivery, send, reply, and forward tools, creator review, and recovery.

## What it enables

**What it enables:** Incoming email can wake the agent. The agent can send, reply, or forward under trust, recipient, content, rate, and review policies you control.

- **Status:** Stable
- **Availability:** Available in `auggy@0.5.0`. Inbound email is off by default; enable polling, WebSocket, or verified webhook delivery with an explicit sender allowlist.
- **Contributes:** `tools`, `transport`, `routes`, `SQLite ledger`, `admin controls`

> **Note: Use AgentMail for email conversations**
>
> Use this augment when incoming email should become an agent turn or the agent needs to continue a thread. Use `notify` for one-way alerts and `visitorAuth` only for magic-link delivery. If you need occasional operator-driven mail actions without an inbox in the runtime, AgentMail MCP may be simpler.

## Install and configure

1. Run ``auggy augment add` agentMail` from the agent project.
2. Run `auggy augment setup agentMail`, or set `AGENTMAIL_API_KEY` and `AGENTMAIL_INBOX_ID` in `.env`.
3. To receive email, choose one inbound mode and add a non-empty exact/domain sender allowlist.
4. If public email turns may reply, add `public` to `outbound.allowedTrustLevels` and keep it in `humanReview.requiredForTrustLevels` so replies queue for creator approval.
5. Keep `webTransport` mounted. Webhook mode needs its verified route; executable human review needs the creator admin route.

**.env**

```env
AGENTMAIL_API_KEY=am_replace_me
AGENTMAIL_INBOX_ID=support@agentmail.to
# Webhook mode only:
AGENTMAIL_WEBHOOK_SECRET=whsec_replace_me
```

**augments/agentMail/augment.yaml**

```yaml
type: agentMail
config:
  apiKey: ${AGENTMAIL_API_KEY}
  inboxId: ${AGENTMAIL_INBOX_ID}
  dbPath: ./agent-mail.db
  outbound:
    allowedTrustLevels:
      - creator
      - public
    allowedRecipients:
      - "*@customer.example"
    subjectPrefix: "[Auggy] "
    maxRecipients: 10
    bodyMaxBytes: 102400
    allowHtml: false
    rateLimit:
      globalMaxPerHour: 10
      perRecipientCooldownMs: 300000
      dedupWindowMs: 300000
    humanReview:
      requiredForTrustLevels:
        - public
      expiresAfterMs: 86400000
  inbound:
    mode: websocket
    allowedSenders:
      - "*@customer.example"
    pollIntervalMs: 60000
    maxPromptBytes: 102400
    maxAttempts: 5
    classifications:
      received: process
      spam: discard
      blocked: discard
      unauthenticated: discard
```

> **Note: Webhook mode**
>
> Replace `mode: websocket` with `mode: webhook` and optionally set `webhook.path`, `webhook.secretEnv`, and `webhook.timestampToleranceSeconds`. Defaults are `/webhooks/agentmail`, `AGENTMAIL_WEBHOOK_SECRET`, and 300 seconds.

## Choose and admit the arrival path

| Mode | Live arrival | Durable recovery | Use when |
| --- | --- | --- | --- |
| none | Disabled | No inbound ledger worker | The agent only sends new email |
| polling | Periodic REST reads | Checkpointed reads are the arrival and catch-up path | A simple one-minute default cadence is enough |
| websocket | Reconnectable AgentMail subscription | REST catch-up runs after subscription | You need low latency without a public callback |
| webhook | Svix-verified POST route | REST catch-up runs at boot; the ledger deduplicates provider retries | The agent has a stable public URL |

Every enabled mode drains the same SQLite ledger. Provider delivery and message IDs are deduplicated; catch-up starts with a 24-hour first-run lookback and advances a checkpoint with one minute of overlap; workers lease one message, retry failed turns with bounded backoff, and durably mark processed or discarded outcomes.

> **Warning: One inbound owner per inbox**
>
> Only one enabled inbound AgentMail augment may own an inbox. Multiple outbound-only instances can coexist, but competing inbound workers are rejected.

| Gate | Default behavior |
| --- | --- |
| Sender | Enabled inbound requires `allowedSenders`; exact addresses and `*@domain` patterns compare case-insensitively. |
| Classification | Ordinary received mail is processed. Spam, blocked, and unauthenticated events are discarded unless explicitly overridden. |
| Identity | The sender becomes a deterministic `public` + `anonymous` peer scoped to the inbox, sender, and thread. |
| Prompt | The provider envelope is rendered as bounded, explicitly untrusted JSON. Email content never becomes a system instruction. |
| Failure | A failed turn retries up to `maxAttempts` (default 5), then the ledger durably discards it. |

## Send, reply, forward, and review

| Tool | Use | Important boundary |
| --- | --- | --- |
| `send_message` | Start a new message | Trust, recipient, content, rate, dedup, and review policy all run before the provider call |
| `reply_to_message` | Continue the triggering email thread | The ID must have been delivered in this turn; real reply recipients are rechecked against policy |
| `forward_message` | Hand the triggering message to another recipient | The ID must have been delivered in this turn and forward recipients are policy-checked |

A configured review trust level receives `status: "`pending_review`"`, not `sent`. The exact queued request is stored with an immutable fingerprint and expires after 24 hours by default. The redacted admin row links to a creator-authenticated, `no-store` detail route; approval requires its fingerprint and rechecks current rate limits.

> **Note: Sensitive content is flagged, not rewritten**
>
> **Auggy** scans outbound text for token-shaped secrets and marks matches in the operator audit surface. It does not block or rewrite the message; use trust, recipient, and review policies to decide whether it can be sent.

> **Warning: Never retry an ambiguous send**
>
> AgentMail sends do not accept an idempotency key. When the provider may have accepted a request before the connection failed, **Auggy** marks the durable attempt ambiguous. Inspect AgentMail and reconcile it as sent or not sent through the admin action; blind retry can duplicate real email.

## Persistence, operations, and limits

Locally, relative AgentMail state resolves from the agent project. On **Railway** it is namespaced under `/app/data/agent-mail/<augment-name>`. Startup requires the real `/app/data` volume, creates the AgentMail state directory with mode 0700, and proves fsync plus atomic rename durability before the agent starts.

- Inspect inbound mode/live state, ledger counts, checkpoint, catch-up summary, last event, worker outcome, and provider error.
- Inspect redacted dispatch and review rows; exact review content requires creator authentication.
- Send a test message, adjust the hourly cap, approve or reject queued mail, and reconcile ambiguous attempts.
- Do not add root-level SQLite symlinks. Core stores resolve directly to the **Railway** volume; only Link retains a compatibility symlink.

- Outbound attachments are not exposed.
- Drafts, read receipts, retraction, and delivery-event turns are not exposed.
- An inbound turn receives the triggering message, not an automatically fetched full thread transcript.
- The `notify` AgentMail adapter and `visitorAuth` magic-link delivery remain separate outbound-only concerns.

## Verify the inbox

1. Run `auggy@0.5.0` and confirm the inbox healthcheck and inbound runtime are ready in the creator admin surface.
2. Send one allowed ordinary email and confirm it becomes a public anonymous turn exactly once.
3. Restart the agent, send mail while it is offline, and confirm REST catch-up processes the missed message without duplicating the earlier one.
4. Send mail from an off-allowlist sender and a discarded classification; confirm neither reaches the model and both outcomes are visible in ledger counts.
5. From an admitted public email turn, propose a reply. Confirm the tool returns ``pending_review``, inspect the exact request as creator, then approve with the matching fingerprint.
6. On **Railway**, confirm the volume is mounted at `/app/data` and AgentMail state lands beneath `/app/data/agent-mail/<augment-name>` after redeploy.

## Related

- [Add an augment](https://auggy.dev/docs/add-augment/markdown)
- [Notify](https://auggy.dev/docs/augment-notify/markdown)
- [Visitor Auth](https://auggy.dev/docs/augment-visitor-auth/markdown)
- [Deploy and verify](https://auggy.dev/docs/deploy/markdown)
- [Security](https://auggy.dev/docs/security/markdown)