Supersedes the Go + embed-Mox design. The Go tree is removed; its
architecture doc is preserved at docs/archive/ARCHITECTURE-go-embed-mox.md
because its competitive analysis and data model still hold.
Five decisions recorded as ADRs:
0001 Rust, not Go — accepting ~5,500 lines of protocol code that Mox
would have given us free, to get the first permissively licensed
Rust mail server. Costs stated plainly.
0002 Apache-2.0, not MIT or AGPL — patent grant, trademark, CLA-free
contribution. Public on GitHub; Gitea stays as the private fallback.
0003 Stalwart's primitive crates (Apache-2.0/MIT) yes; its AGPL server
crates never. DANE and MTA-STS sit on the AGPL side of that line,
which is why we write our own.
0004 Milestones, reordered: embedded inbound is required at launch.
0005 Oracle Cloud blocks outbound :25, so direct-to-MX is impossible on
the launch host. Split delivery is mandatory, not an on-ramp.
Twelve crates in three tiers. Tier 1 (mail-dane, mail-mta-sts, mail-dsn)
is standalone and publishable — no `dane` or `mta-sts` crate exists on
crates.io at all today.
openmail-relay ships the provider table as data, with SES and Oracle from
the start. Oracle's and Resend's SPF includes are deliberately None: a
guessed include turns the DNS check green against a mechanism the provider
does not honour, and mail still fails SPF silently.
cargo check/test/clippy/fmt all green; unsafe_code is forbidden workspace
wide; cargo-deny enforces the licence policy in CI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkyvfNJGTshJNE9FtwPLk7
141 lines
6.3 KiB
Markdown
141 lines
6.3 KiB
Markdown
<h1>OpenMail</h1>
|
|
|
|
**An agent-native, self-hosted mail server, written in Rust.**
|
|
|
|
One binary that gives an AI agent its own real email address — receive, parse,
|
|
thread, search and send actual SMTP mail on infrastructure you control — behind
|
|
a clean REST API and an MCP server. Humans and agents are both first-class
|
|
users.
|
|
|
|
[](./LICENSE)
|
|
[](./docs/adr/0004-milestones.md)
|
|
|
|
> **Status: v0.1, work in progress, not yet released.** The workspace compiles
|
|
> and the domain model is taking shape; it does not yet send or receive mail.
|
|
> Follow [`docs/adr/0004-milestones.md`](./docs/adr/0004-milestones.md).
|
|
|
|
---
|
|
|
|
## Why this exists
|
|
|
|
The hard part of an agent-mailbox product was never the API. It is the mail
|
|
plumbing: receiving over SMTP/MX, sending with real deliverability
|
|
(SPF/DKIM/DMARC, DANE/MTA-STS, IP reputation), parsing genuinely broken MIME,
|
|
threading, and storage.
|
|
|
|
Hosted agent-mail products solve this well and run on someone else's
|
|
infrastructure, closed. The self-hostable mail servers that exist —
|
|
Postfix+Dovecot, iRedMail, Stalwart — solve the plumbing but have no notion of
|
|
an agent: no per-agent inbox provisioning, no threads as API resources, no MCP,
|
|
no way for an agent to own a mailbox.
|
|
|
|
**OpenMail is the intersection nobody occupies: agent-native, self-hostable,
|
|
and permissively licensed.**
|
|
|
|
## What makes it agent-native
|
|
|
|
- **Inboxes are API resources**, provisioned in one call — not Unix accounts.
|
|
- **Threads are first-class**, stitched from `In-Reply-To`/`References`. An
|
|
agent asks for a conversation, not a folder listing.
|
|
- **`extracted_text`** — the reply with quoted history stripped. An agent that
|
|
reads full bodies re-reads the whole thread every turn and burns its context
|
|
window on text it already has.
|
|
- **MCP server** — an agent owns and operates its own mailbox as tools.
|
|
- **Webhooks + WebSocket** `message.received` events. Push, not poll.
|
|
- **Humans too** — standard IMAP/SMTP access is a first-class goal, not an
|
|
afterthought, so a person can point Apple Mail or Thunderbird at the same
|
|
mailbox an agent is driving.
|
|
|
|
## Why Rust, and why our own crates
|
|
|
|
Two implementations of the mail plumbing exist in a permissive licence: Mox
|
|
(MIT, Go) and — for the primitives only — Stalwart's published crates
|
|
(Apache-2.0/MIT, Rust). Stalwart's *server* is AGPL-3.0, which is why nobody
|
|
has shipped a permissively licensed Rust mail server.
|
|
|
|
We are building one. See [`docs/adr/0001-rust.md`](./docs/adr/0001-rust.md) for
|
|
the decision and its honest costs.
|
|
|
|
Concretely, this means writing what the Rust ecosystem does not have. At the
|
|
time of writing, **`dane` and `mta-sts` do not exist on crates.io at all** —
|
|
Stalwart keeps its implementations inside AGPL server crates. Ours ship
|
|
standalone and permissive, so any Rust mail project can use them.
|
|
|
|
## The workspace
|
|
|
|
Twelve crates in three tiers. Tier 1 is published to crates.io as a
|
|
contribution to the Rust mail ecosystem and depends on nothing else here.
|
|
|
|
### Tier 1 — standalone, publishable
|
|
|
|
| Crate | What | Prior art in Rust |
|
|
|---|---|---|
|
|
| [`mail-dane`](./crates/mail-dane) | DANE / TLSA verification for SMTP (RFC 7672) | **none — first permissive implementation** |
|
|
| [`mail-mta-sts`](./crates/mail-mta-sts) | MTA-STS policy discovery, fetch, parse, cache (RFC 8461) | **none — first permissive implementation** |
|
|
| [`mail-dsn`](./crates/mail-dsn) | Delivery Status Notifications (RFC 3464) | none |
|
|
|
|
### Tier 2 — the mail engine
|
|
|
|
| Crate | What |
|
|
|---|---|
|
|
| [`openmail-smtpd`](./crates/openmail-smtpd) | Inbound SMTP: session state machine, STARTTLS, AUTH, PIPELINING |
|
|
| [`openmail-relay`](./crates/openmail-relay) | Outbound: smarthost relays (SES, Oracle, generic) and direct-to-MX |
|
|
| [`openmail-guard`](./crates/openmail-guard) | Abuse gate: iprev, DNSBL, rate limiting |
|
|
| [`openmail-junk`](./crates/openmail-junk) | Per-inbox Bayesian spam classification |
|
|
|
|
### Tier 3 — the agent-native layer (the product)
|
|
|
|
| Crate | What |
|
|
|---|---|
|
|
| [`openmail-core`](./crates/openmail-core) | Domain model, threading, quote-stripping. No I/O. |
|
|
| [`openmail-store`](./crates/openmail-store) | Postgres metadata + S3-compatible blobs |
|
|
| [`openmail-api`](./crates/openmail-api) | The v0 REST API |
|
|
| [`openmail-mcp`](./crates/openmail-mcp) | MCP server |
|
|
| [`openmail`](./crates/openmail) | The binary: `serve`, `smtpd`, `sender`, `mcp`, `migrate` |
|
|
|
|
### Third-party
|
|
|
|
`mail-parser`, `mail-builder`, `mail-auth` (DKIM/DKIM2/SPF/DMARC/ARC),
|
|
`smtp-proto` — all Apache-2.0 OR MIT, all from Stalwart Labs' separately
|
|
published primitive crates — plus `hickory-resolver` for DNS and DNSSEC.
|
|
|
|
**No AGPL, GPL, or LGPL code is linked into any OpenMail binary.** We use none
|
|
of the Stalwart *server*. See [`NOTICE`](./NOTICE) and
|
|
[`docs/adr/0003-own-crates.md`](./docs/adr/0003-own-crates.md).
|
|
|
|
## Sending: bring your own reputation, or build your own
|
|
|
|
Outbound sits behind one interface with two paths:
|
|
|
|
- **Relay** — SES, Oracle Cloud Email Delivery, SendGrid, Postmark, Resend, or
|
|
any smarthost. Rents someone else's IP reputation; inbox placement on day
|
|
one. Providers are declarative data, not special cases —
|
|
[`crates/openmail-relay/src/providers.rs`](./crates/openmail-relay/src/providers.rs).
|
|
- **Direct-to-MX** — we resolve MX and deliver ourselves, with MTA-STS and DANE
|
|
enforced. Our reputation, our control, and a months-long IP warmup.
|
|
|
|
Receiving is always ours.
|
|
|
|
> ⚠️ **Oracle Cloud blocks outbound TCP/25** for tenancies created after
|
|
> 2021-06-23. Inbound :25 is unaffected. So on OCI you *receive* directly and
|
|
> *relay* outbound on 587 — direct-to-MX is not possible there at all.
|
|
> [`docs/adr/0005-oracle-cloud.md`](./docs/adr/0005-oracle-cloud.md).
|
|
|
|
## Build
|
|
|
|
```bash
|
|
cargo check --workspace # ~21s cold on a Ryzen 7 5800X
|
|
cargo test --workspace
|
|
cargo clippy --workspace --all-targets # zero warnings is the gate
|
|
```
|
|
|
|
`unsafe_code = "forbid"` across the workspace. This code parses hostile input
|
|
from the open internet on port 25; there is no exception worth the risk.
|
|
|
|
## Licence
|
|
|
|
**Apache-2.0.** Permissive on purpose: the point is that other people can build
|
|
commercial products on top of this, including ones that compete with anything
|
|
we might host later. See [`docs/adr/0002-apache-2.md`](./docs/adr/0002-apache-2.md)
|
|
for why Apache-2.0 rather than MIT or AGPL.
|