> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mail Quickstart

> Bring up Stalwart and StateSet Mail with Docker, then send a message that round-trips back to the inbox API.

# Mail Quickstart

This brings up the whole stack — Stalwart Mail Server plus StateSet Mail — and sends a message
that round-trips back to the inbox API.

## 1. Start Stalwart

```bash theme={null}
cp .env.example .env
# generate a long API key into .env:
sed -i 's/replace-me-with-a-long-random-string/'"$(openssl rand -hex 24)"'/' .env

docker compose up -d stalwart
```

Wait about 20 seconds, then grab the auto-generated admin password:

```bash theme={null}
docker logs stateset-stalwart 2>&1 | grep -i "password"
```

## 2. Configure Stalwart

Open `http://localhost:8080` and sign in as `admin` with the printed password.

* **Settings → Domains** — add `local.test`. Any domain works for local testing; mail won't
  leave the box.
* **Management → Accounts** — create an account `agent` in the `local.test` domain and set a
  password.

You now have a mailbox `agent@local.test` that can both send and receive locally.

## 3. Plug the credentials into `.env`

```bash theme={null}
SMTP_USERNAME=agent@local.test
SMTP_PASSWORD=<the password you just set>
IMAP_USERNAME=agent@local.test
IMAP_PASSWORD=<the password you just set>
DEFAULT_FROM="AI Agent <agent@local.test>"
```

## 4. Start StateSet Mail

```bash theme={null}
docker compose up -d stateset-mail
docker logs -f stateset-mail
```

You should see `outbound worker started`, `inbox worker starting`, and `listening`.

## 5. Send a message

```bash theme={null}
curl -X POST http://localhost:3000/v1/messages \
  -H "Authorization: Bearer $STATESET_MAIL_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "to": "agent@local.test",
    "subject": "Round trip",
    "text_body": "Hello from the API."
  }'
```

The outbound worker relays it through Stalwart; the IMAP worker polls it back. Read it:

```bash theme={null}
curl http://localhost:3000/v1/inbox/messages \
  -H "Authorization: Bearer $STATESET_MAIL_API_KEY"
```

<Tip>
  Pass an `Idempotency-Key` header so a retried call doesn't double-send.
</Tip>

## Running natively

The service is a single Rust binary against one SQLite file — Docker is a convenience, not a
requirement. Set the same environment variables and run it directly; `DATABASE_URL` defaults to
`sqlite://data/mail.db` with WAL mode enabled.

## Set up tracking

Opens, clicks, and `List-Unsubscribe` all need a public origin so the recipient's mail client
can reach back:

```bash theme={null}
PUBLIC_BASE_URL=https://mail.example.com
```

Without it, tracking and one-click unsubscribe are disabled. Every emitted `/t/*` URL is
HMAC-signed with `TRACKING_SECRET` (which defaults to the first API key) — rotate it carefully,
since rotation invalidates in-flight links.

## Next steps

<CardGroup cols={2}>
  <Card title="Overview" icon="book" href="/stateset-mail/overview">
    The full feature surface.
  </Card>

  <Card title="Conversations" icon="comments" href="/stateset-mail/conversations">
    Triage threads instead of messages.
  </Card>

  <Card title="MCP server" icon="plug" href="/stateset-mail/mcp-server">
    Wire it up to Claude.
  </Card>

  <Card title="Configuration" icon="gear" href="/stateset-mail/configuration">
    Every environment variable.
  </Card>
</CardGroup>
