Store: the full Postgres schema as an embedded migration. UUIDv7 keys so `ORDER BY id` is a free chronological index; raw MIME and attachments live in object storage with only a key in the row; `pods` present from day one because retrofitting tenancy costs more than an unused column. API keys are stored as a SHA-256 hash — a database dump must not be a set of live credentials. API: the v0 route table, including `ingest`, which closes the receive→thread→extract loop with zero mail infrastructure and is what makes the agent layer testable in CI. Scopes are a closed enum rather than strings, so "can send mail" and "can mint keys" are not one typo apart. Internal errors are logged in full and reported as a bare string. MCP: the tool catalogue, six tools. Adding a row here is the only way an agent gains a capability — a new REST route is invisible until someone opts it in. Three tests guard the rule that no tool can ever reach key management; CI fails rather than production. ADR 0006: enterprise self-hosted first. A hosted offering comes only after we have run this ourselves long enough to have a deliverability record worth selling. `pods` stays in the schema as the thing that keeps that path open — do not remove it as dead code. Also: multi-stage Dockerfile running as a non-root system user with no shell in the runtime image, and the openmail.karti.ai static page. 17 tests, zero clippy warnings, fmt clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JkyvfNJGTshJNE9FtwPLk7
OpenMail
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.
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.
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.receivedevents. 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 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 |
DANE / TLSA verification for SMTP (RFC 7672) | none — first permissive implementation |
mail-mta-sts |
MTA-STS policy discovery, fetch, parse, cache (RFC 8461) | none — first permissive implementation |
mail-dsn |
Delivery Status Notifications (RFC 3464) | none |
Tier 2 — the mail engine
| Crate | What |
|---|---|
openmail-smtpd |
Inbound SMTP: session state machine, STARTTLS, AUTH, PIPELINING |
openmail-relay |
Outbound: smarthost relays (SES, Oracle, generic) and direct-to-MX |
openmail-guard |
Abuse gate: iprev, DNSBL, rate limiting |
openmail-junk |
Per-inbox Bayesian spam classification |
Tier 3 — the agent-native layer (the product)
| Crate | What |
|---|---|
openmail-core |
Domain model, threading, quote-stripping. No I/O. |
openmail-store |
Postgres metadata + S3-compatible blobs |
openmail-api |
The v0 REST API |
openmail-mcp |
MCP server |
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 and
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. - 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.
Build
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
for why Apache-2.0 rather than MIT or AGPL.