Rewrite as a Rust, Apache-2.0 workspace
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
This commit is contained in:
co-authored by
Claude Opus 5
parent
428040d964
commit
36b15ddcaf
@@ -1,55 +1,140 @@
|
||||
# OpenMail
|
||||
<h1>OpenMail</h1>
|
||||
|
||||
**An agent-native, self-hosted mail server.** One Go binary that gives an AI agent its own real
|
||||
email address — receive, parse, thread, search, and send actual SMTP mail on a box you control —
|
||||
behind a clean REST API and an MCP server.
|
||||
**An agent-native, self-hosted mail server, written in Rust.**
|
||||
|
||||
Think "AgentMail, but self-hosted and MIT-licensed." OpenMail embeds the battle-tested mail
|
||||
internals of [Mox](https://github.com/mjl-/mox) (also MIT) for the hard, correctness-critical
|
||||
plumbing — DKIM, SPF/DMARC, DANE + MTA-STS secure delivery, real-world MIME parsing, spam
|
||||
filtering — and layers a native, agent-shaped data model (Postgres + object storage) and API on top.
|
||||
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.
|
||||
|
||||
Mail I/O is **pluggable**: start in minutes against a relay (SES/Postmark/Resend) or your existing
|
||||
mailbox (IMAP/SMTP), and graduate to a fully self-hosted, in-process SMTP engine when you want to own
|
||||
the whole stack. The in-process engine is the part nobody else ships — the only comparable project,
|
||||
[agenticmail](https://github.com/agenticmail/agenticmail), runs a Stalwart (AGPL) mail server in a
|
||||
Docker sidecar; OpenMail is **one static binary, fully MIT, no sidecar.**
|
||||
[](./LICENSE)
|
||||
[](./docs/adr/0004-milestones.md)
|
||||
|
||||
> **Status: early WIP, private during initial build.** Will be released MIT-licensed and public.
|
||||
> Designed only from public RFCs and public API surfaces — nothing proprietary.
|
||||
> **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).
|
||||
|
||||
License: **MIT** — see [LICENSE](./LICENSE). Builds on Mox (MIT) and the `emersion/go-*` mail
|
||||
libraries. See **[ARCHITECTURE.md](./ARCHITECTURE.md)** for the design of record.
|
||||
---
|
||||
|
||||
## Why
|
||||
## Why this exists
|
||||
|
||||
The valuable, hard part of an agent-mailbox product is not the API — it's the mail plumbing:
|
||||
receiving over SMTP/MX, *sending with real deliverability* (SPF/DKIM/DMARC, DANE/MTA-STS, IP
|
||||
reputation), parsing messy MIME, threading, and storage. Hosted products (AgentMail and similar)
|
||||
solve this well but are closed and run on someone else's infrastructure. OpenMail's bet: you can
|
||||
**embed** an existing MIT-licensed, production-grade Go mail stack instead of rebuilding it, and
|
||||
spend your effort on the part nobody has done well — the **agent-native** layer.
|
||||
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
|
||||
|
||||
- **Persistent inboxes as first-class API resources**, provisioned in one call.
|
||||
- **Structured threads**, not raw IMAP — `In-Reply-To`/`References` stitched into conversations.
|
||||
- **`extracted_text`** — reply content with quoted history stripped, so an agent reads the new part.
|
||||
- **MCP server** — an agent (Claude Code, etc.) owns and operates its mailbox directly as tools.
|
||||
- **Webhooks + WebSocket** `message.received` events — agents react to mail in real time.
|
||||
- **AgentMail-API-shaped** REST where reasonable, so existing tooling points at a self-hosted base URL.
|
||||
- **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.
|
||||
|
||||
## Goals
|
||||
## Why Rust, and why our own crates
|
||||
|
||||
- **Self-hostable** in one `docker compose up` on a single VPS; scales to a fleet later.
|
||||
- **Deliverability taken seriously** — self-host SMTP send with DKIM + DANE + MTA-STS via Mox's
|
||||
delivery stack, *or* a relay backend (SES/Postmark/Resend) for inbox placement on day one.
|
||||
- **Single static Go binary** with subcommands; Postgres + S3-compatible object store as the only deps.
|
||||
- **Genuinely MIT** — every embedded dependency is MIT/BSD; no GPL/AGPL anywhere in the tree.
|
||||
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.
|
||||
|
||||
## Non-goals (for v1)
|
||||
We are building one. See [`docs/adr/0001-rust.md`](./docs/adr/0001-rust.md) for
|
||||
the decision and its honest costs.
|
||||
|
||||
- A hosted multi-tenant SaaS. OpenMail is self-host-first (multi-tenant `pods` exist, but you run it).
|
||||
- A full webmail UI. The product is the API + MCP; humans use their own client.
|
||||
- Beating a mature provider's deliverability on day one — self-host IP reputation takes warmup + time;
|
||||
the relay backend exists for exactly that gap.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user