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

# Stalwart

# Stalwart Mail Server

Self-hosted mail server with a privacy-focused architecture that keeps the home IP hidden.

## Overview

Stalwart is a modern, open-source mail server written in Rust that supports JMAP, IMAP, POP3, SMTP, CalDAV, CardDAV, and WebDAV.

* **Hostname:** `stalwart.augustin.ai`
* **Primary Domain:** `augustin.ai`
* **Primary Email:** `root@augustin.ai`
* **Catch-all:** `*@augustin.ai` → routes to primary account

:::note
Stalwart does not include a built-in webmail client. The web interface at `stalwart.augustin.ai` is for administration only. To read emails, use an IMAP client (Apple Mail, Thunderbird, etc.) or deploy a separate webmail container. Stalwart plans to add webmail in 2026.
:::

## Architecture

```
Inbound:  Internet → Cloudflare MX → Email Routing → Worker → JMAP API → Stalwart
Outbound: Stalwart → Resend SMTP relay → Internet
Access:   IMAP via Tailscale (iPhone) / Cloudflare Tunnel TCP (laptop)
```

## Components

| Component                | Purpose                           | Status          |
| ------------------------ | --------------------------------- | --------------- |
| Stalwart                 | Mail server                       | ✅ Running       |
| Cloudflare Email Routing | Receive inbound mail              | ✅ Configured    |
| Cloudflare Worker        | Forward mail to Stalwart via JMAP | ✅ Deployed      |
| Cloudflare Tunnel (HTTP) | Admin/JMAP access                 | ✅ Working       |
| Cloudflare Tunnel (TCP)  | IMAP for work laptop              | 🔲 To configure |
| Tailscale                | IMAP for iPhone                   | 🔲 To set up    |
| Resend                   | Outbound SMTP relay               | ✅ Configured    |

## Storage

| Store            | Purpose                     | Backend |
| ---------------- | --------------------------- | ------- |
| Data Store       | Metadata, folders, settings | RocksDB |
| Blob Store       | Emails, attachments         | RocksDB |
| Full-text Search | Email search indexing       | RocksDB |
| Lookup Store     | Caching, sessions           | RocksDB |

## Ports

| Port | Protocol      | Purpose                    | Access Method             |
| ---- | ------------- | -------------------------- | ------------------------- |
| 25   | SMTP          | Receiving external mail    | Not used (using Worker)   |
| 465  | SMTPS         | Client mail submission     | Tailscale / CF Tunnel TCP |
| 587  | SMTP+STARTTLS | Client mail submission     | Tailscale / CF Tunnel TCP |
| 993  | IMAPS         | Client mail retrieval      | Tailscale / CF Tunnel TCP |
| 443  | HTTPS         | Admin, JMAP API            | Traefik / CF Tunnel HTTP  |
| 8080 | HTTP          | Internal (Traefik backend) | Not exposed               |
| 4190 | ManageSieve   | Mail filter rules          | Optional                  |

## Setup Progress

### Phase 1: Core Deployment ✅

* [x] Create compose.yaml
* [x] Create .env file with initial config
* [x] Deploy container
* [x] Access web admin, get initial password
* [x] Configure hostname in Stalwart

### Phase 2: Storage Configuration ✅

* [x] Configure storage in Stalwart web admin (B2 for blobs, RocksDB for data/fts/lookup)
* [x] Test storage is working (verified email blob stored in B2)

### Phase 3: Outbound Mail (Resend) ✅

* [x] Create Resend account
* [x] Verify domain in Resend (added DKIM record to Cloudflare)
* [x] Get SMTP credentials
* [x] Configure Stalwart to relay through Resend (smtp.resend.com:465)
* [x] Test outbound mail

### Phase 4: Client Access

* [x] Configure Traefik labels for HTTP access
* [x] Cloudflare Tunnel working for HTTP (admin/JMAP)
* [x] Configure `http.url` for reverse proxy (JMAP returns HTTPS URLs)
* [ ] Set up Cloudflare Tunnel for TCP (IMAP)
* [ ] Install Tailscale on server
* [ ] Test iPhone Mail via Tailscale
* [ ] Test work laptop via CF Tunnel TCP

### Phase 5: Inbound Mail (Cloudflare Worker) ✅

* [x] Write Cloudflare Worker for email → JMAP
* [x] Deploy Worker to Cloudflare
* [x] Configure Cloudflare Email Routing
* [x] Set up MX records
* [x] Configure catch-all `*@augustin.ai` in Stalwart
* [x] Test inbound mail delivery

### Phase 6: Domain & DNS ✅

* [x] Add domain in Stalwart
* [x] Configure SPF, DKIM, DMARC, TLS reporting
* [x] Create user account
* [x] Test end-to-end email flow

## DNS Records

### Currently Configured ✅

| Type | Name                             | Content                                                                                    |
| ---- | -------------------------------- | ------------------------------------------------------------------------------------------ |
| MX   | `augustin.ai`                    | `route1.mx.cloudflare.net` (priority 50)                                                   |
| MX   | `augustin.ai`                    | `route2.mx.cloudflare.net` (priority 64)                                                   |
| MX   | `augustin.ai`                    | `route3.mx.cloudflare.net` (priority 82)                                                   |
| TXT  | `augustin.ai`                    | `v=spf1 include:_spf.mx.cloudflare.net ~all`                                               |
| TXT  | `_dmarc.augustin.ai`             | `v=DMARC1; p=reject; rua=mailto:postmaster@augustin.ai; ruf=mailto:postmaster@augustin.ai` |
| TXT  | `202602r._domainkey.augustin.ai` | RSA DKIM key                                                                               |
| TXT  | `202602e._domainkey.augustin.ai` | Ed25519 DKIM key                                                                           |
| TXT  | `_smtp._tls.augustin.ai`         | `v=TLSRPTv1; rua=mailto:postmaster@augustin.ai`                                            |
| TXT  | `resend._domainkey.augustin.ai`  | Resend DKIM key                                                                            |

### Optional (Not Yet Configured)

| Type  | Name                            | Content                        | Purpose                     |
| ----- | ------------------------------- | ------------------------------ | --------------------------- |
| CNAME | `autoconfig.augustin.ai`        | `stalwart.augustin.ai`         | Email client auto-config    |
| CNAME | `autodiscover.augustin.ai`      | `stalwart.augustin.ai`         | Email client auto-discovery |
| SRV   | `_imaps._tcp.augustin.ai`       | `0 1 993 stalwart.augustin.ai` | IMAP service discovery      |
| SRV   | `_submissions._tcp.augustin.ai` | `0 1 465 stalwart.augustin.ai` | SMTP service discovery      |
| SRV   | `_jmap._tcp.augustin.ai`        | `0 1 443 stalwart.augustin.ai` | JMAP service discovery      |

## Cloudflare Worker

The Worker (`cloudflare-worker.js`) handles inbound email:

1. Receives raw email from Cloudflare Email Routing
2. Looks up recipient in Stalwart accounts (supports catch-all)
3. Gets JMAP session to find account ID
4. Uploads email blob via JMAP
5. Imports email to recipient's Inbox

## Client Configuration

### JMAP Clients

| Setting  | Value                                           |
| -------- | ----------------------------------------------- |
| JMAP URL | `https://stalwart.augustin.ai/.well-known/jmap` |
| Username | `root@augustin.ai`                              |

### IMAP/SMTP Clients

| Setting     | Value                                |
| ----------- | ------------------------------------ |
| IMAP Server | `stalwart.augustin.ai:993` (SSL/TLS) |
| SMTP Server | `stalwart.augustin.ai:465` (SSL/TLS) |
| Username    | `root@augustin.ai`                   |

:::note
IMAP/SMTP require direct access via Tailscale or Cloudflare Tunnel TCP (not yet configured).
:::

## Backup & Recovery

### Backup Strategy

All email data is local in RocksDB (blobs, metadata, mailbox structure, FTS index, settings).

A nightly JMAP export runs at 2:45am via `stalwart/backup-email.sh`, using `stalwart-cli export account` to dump the full account (emails, mailboxes, identities, Sieve scripts, vacation responses) into `stalwart/data/export/`. The 3:00am restic backup then sends this to AWS S3.

| Backup Layer | What                             | Where                          | Schedule     |
| ------------ | -------------------------------- | ------------------------------ | ------------ |
| JMAP export  | Full account (emails + config)   | `stalwart/data/export/`        | Daily 2:45am |
| Restic → S3  | All of `~/apps` including export | AWS S3 (`augustin-backups/i3`) | Daily 3:00am |

### Recovery Scenarios

1. **Container dies (data intact)** — `docker compose up -d`, Stalwart reconnects to RocksDB automatically
2. **Full restore from restic** — restore `stalwart/` from restic, start container
3. **Clean slate rebuild** — restore from restic, deploy fresh container, reimport via `stalwart-cli import account`
4. **Stalwart is down, need to read email** — inbound mail queues at Cloudflare (\~24h retry), latest JMAP export on disk contains raw `.eml` files

See `stalwart/README.md` for detailed recovery commands.

## Future Enhancements

* [ ] N8N integration via JMAP for AI email parsing
* [ ] CalDAV/CardDAV for calendar and contacts
* [ ] Pocket ID integration (OpenID Connect)
* [ ] Multiple domains
* [ ] Shared mailboxes
* [ ] Webmail container (Roundcube or SnappyMail)
