Architecture v2: embed Mox (MIT) as a library; native Postgres agent layer

- Choose Option B: single binary embedding Mox's stateless protocol/crypto/
  delivery packages (dkim, spf, dmarc, dane, mtasts, message, smtpclient, junk,
  dsn, anti-abuse) while owning server loop (go-smtp) + Postgres data model.
- Boundary rule: never import mox smtpserver/imapserver/queue/store/config
  (bbolt/global-config coupled) — that's the line between embed and fork.
- Add MIT LICENSE; reframe README as a real MIT OSS product.
- Deliverability moat leans on mox smtpclient+dane+mtasts; relay fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
karti-ai
2026-06-21 12:07:37 -07:00
parent 9812e25973
commit aab2a8558e
3 changed files with 195 additions and 117 deletions
+132 -86
View File
@@ -1,112 +1,145 @@
# OpenMail — Architecture
Self-hosted, AI-native mailbox for agents. One Go service, Postgres, and object storage on a VPS,
fronted by Caddy for TLS. This document is the design of record; it favors an MVP that actually
receives and sends real mail over a perfect feature set.
Agent-native, self-hosted mail server. **One Go binary** that embeds [Mox](https://github.com/mjl-/mox)'s
(MIT) mail internals for the correctness-critical 95% — DKIM, SPF/DMARC, DANE + MTA-STS delivery,
real-world MIME, spam filtering — and owns a **native, agent-shaped data model** (Postgres + object
store) with a REST API and MCP server on top. This document is the design of record. It favors an
MVP that actually receives and sends real mail over a perfect feature set.
## 0. The honest framing
## 0. The decision: embed Mox, don't rebuild and don't just wrap it
An agent-mailbox API is ~5% of the work. The other 95% is **mail infrastructure**:
An agent-mailbox API is ~5% of the work. The other 95% is mail infrastructure (inbound :25 hardening,
outbound deliverability, MIME, threading, quote-stripping, anti-abuse). Three ways to get that 95%:
| Hard part | Why it's hard |
|---|---|
| **Inbound** | You must run an SMTP server on port 25 with MX records pointing at it, accept from arbitrary senders, survive spam/abuse, and parse messy real-world MIME. |
| **Outbound deliverability** | Anyone can send SMTP; getting it to the *inbox* needs SPF + DKIM signing + DMARC alignment, PTR/rDNS, warmed IP reputation, feedback loops (FBLs), and bounce/complaint handling. This is the real moat. |
| **MIME parsing** | Multipart trees, encodings, inline vs attachment, calendar parts, broken senders. |
| **Threading** | Stitching messages into conversations from `Message-ID` / `In-Reply-To` / `References` plus subject heuristics. |
| **Quoted-history stripping** | The `extracted_text` feature (reply content without the quoted thread) is its own parsing problem. |
1. **Build from scratch** — re-implement DKIM/SPF/DMARC/DANE/MTA-STS/MIME ourselves. Correct but slow,
and we'd do it worse than a production server at first.
2. **Sidecar an existing server** (run Mox as a separate process, read it over IMAP). Fast, but we
don't own the data model — threads/labels/`extracted_text` would be re-derived from IMAP, and we
ship two coupled processes.
3. **Embed Mox's packages** into our own binary and own the storage + API. ← **chosen.**
OpenMail is structured so each of these is an isolated, swappable component, and so v1 can lean on a
relay for deliverability while the self-hosted SMTP path matures.
Why (3) is viable: **Mox has no `internal/` directory** — every package is importable — and the
README states *"most non-server Go packages are written to be reusable."* The real boundary is not
visibility, it's **storage coupling**: Mox's *server + store* packages are welded to its embedded
`bstore`/`bbolt` store and global config; its *protocol/crypto/parsing* packages are stateless and
clean. We embed the latter and replace the former with our own Postgres-backed server.
### What we embed from Mox vs. what we own
| Concern | Source | Notes |
|---|---|---|
| MIME parsing | **mox `message`** | Battle-tested on real, broken senders. Better than enmime for our needs. |
| DKIM sign + verify | **mox `dkim`** | |
| SPF / DMARC | **mox `spf`, `dmarc`** | Inbound auth verification + policy. |
| Secure outbound delivery | **mox `smtpclient`, `dane`, `mtasts`, `dns`** | The deliverability moat — DANE/MTA-STS MX delivery, mostly solved by reusing this. |
| Bounces (DSN) | **mox `dsn`** | Parse/generate delivery status notifications. |
| Anti-abuse | **mox `iprev`, `dnsbl`, `ratelimit`** | rDNS check, blocklists, rate limiting on :25. |
| Spam filtering | **mox `junk`** | Built-in Bayesian filter; per-inbox train/score. |
| SMTP auth | **mox `sasl`, `scram`** | For the submission (send) path. |
| — | — | — |
| Inbound SMTP **server loop** | **`emersion/go-smtp`** | Clean, embeddable listener/session. We do NOT import mox `smtpserver` (store-coupled). |
| Storage | **OpenMail (Postgres + S3)** | Native agent data model. NOT mox `store`/bbolt. |
| API, MCP, threading, extraction, webhooks, auth, multi-tenancy | **OpenMail** | The agent-native layer — our differentiation. |
> **Boundary rule:** never import `smtpserver`, `imapserver`, `queue`, `store`, or `mox-` (config) —
> they assume Mox's storage/config. Importing them is the line between "embed as a library" and
> "fork Mox." We stay on the library side.
## 1. Component overview
```
┌──────────────────────────────────────────────┐
Internet senders ───▶ │ smtpd (inbound, port 25, go-smtp)
(MX → this VPS) │ → parse MIME → normalize → enqueue
└───────────────┬──────────────────────────────┘
│ internal queue (Postgres LISTEN/NOTIFY or NATS)
┌───────────────▼──────────────────────────────┐
┌──────────────────────────────────────────────────────
Internet senders ───▶ │ smtpd (go-smtp on :25)
(MX → this VPS) │ iprev/dnsbl/ratelimit → SPF/DKIM/DMARC verify
│ → mox/message parse → mox/junk score → enqueue │ ← mox pkgs
└───────────────┬──────────────────────────────────────┘
│ internal queue (Postgres LISTEN/NOTIFY)
┌───────────────▼──────────────────────────────────────┐
│ core (Go) │
Agents / SDKs ──REST──▶ • inbox/message/thread/draft services │
MCP clients ───MCP──▶ │ • threading, extraction, labels, search │
MCP clients ───MCP──▶ │ • threading, extracted_text, labels, FTS search │
│ • auth (API keys, pods), webhooks/WS │
└──────┬──────────────────────────────────────┘
└──────┬───────────────────────────────┬────────────────┘
│ │
┌────────────▼─────┐ ───────────▼──────────────
┌────────────▼─────┐ ┌─────────────▼──────────────┐
│ Postgres │ │ Object store (S3/MinIO) │
│ metadata + FTS │ │ raw MIME + attachments │
│ metadata + FTS │ │ raw .eml + attachments │
└──────────────────┘ └────────────────────────────┘
┌───────────────┴──────────────────────────────┐
Outbound to world ◀── │ sender (DKIM-sign → self-host SMTP OR relay)
└──────────────────────────────────────────────┘
┌───────────────┴──────────────────────────────────────
Outbound to world ◀── │ sender (mox/dkim sign → backend)
│ self-host: mox/smtpclient + dane + mtasts (MX) │ ← mox pkgs
│ relay: SES / Postmark / Resend API │
└──────────────────────────────────────────────────────┘
```
All four roles (`smtpd`, `core` API, `sender`, MCP) ship in one Go binary with subcommands; deploy
together or split later. Caddy terminates TLS for the HTTP API and auto-manages certs.
All roles (`smtpd`, `core` API, `sender`, `mcp`) ship in one binary with subcommands; deploy together
or split later. Caddy terminates TLS for the HTTP API and auto-manages certs.
## 2. Tech stack
- **Language:** Go (single static binary; matches the deployment-simplicity goal and OpenMail's
systems nature).
- **Language:** Go single static binary, and the whole mature self-hosted-mail ecosystem (Mox,
go-smtp, go-message) is Go, keeping us MIT-compatible. Rust (Stalwart) is AGPL → ruled out.
- **HTTP:** `chi` router; `sqlc` + `pgx` for typed Postgres access.
- **Inbound SMTP:** `emersion/go-smtp` (server) — listens on :25, hands raw bytes to the parser.
- **MIME:** `emersion/go-message` (+ `enmime` for lenient real-world parsing); a quote-stripper
(port of `talon`/`EmailReplyParser` heuristics) for `extracted_text`/`extracted_html`.
- **DKIM:** `emersion/go-msgauth/dkim` for signing (outbound) and verifying (inbound).
- **Outbound:** pluggable `Sender` interface — `smtp` (self-host, MX delivery) or `relay`
(SES / Postmark / Resend API). Default to relay in v1 for deliverability; self-host is opt-in.
- **Storage:** Postgres (metadata + full-text search via `tsvector`); S3-compatible object store
(MinIO self-hosted, or AWS S3) for raw `.eml` and attachment blobs.
- **Events:** Postgres `LISTEN/NOTIFY` for the internal inbound queue and for fanning out
webhooks + a WebSocket stream. (Swap to NATS if volume warrants.)
- **Inbound server:** `emersion/go-smtp` listener → handed to mox packages for verify/parse/score.
- **Mail correctness/delivery:** mox `message`, `dkim`, `spf`, `dmarc`, `dane`, `mtasts`, `dns`,
`smtpclient`, `dsn`, `junk`, `iprev`, `dnsbl`, `ratelimit`, `sasl`, `scram` (see §0 table).
- **Quote stripping:** a `talon`/`EmailReplyParser`-style heuristic pass for `extracted_text`.
- **Storage:** Postgres (metadata + `tsvector` FTS); S3-compatible object store (MinIO self-host, or
S3) for raw `.eml` and attachment blobs.
- **Events:** Postgres `LISTEN/NOTIFY` for the internal inbound queue and to fan out webhooks + a
WebSocket stream. (Swap to NATS only if volume warrants.)
- **Deploy:** `docker compose``caddy`, `openmail` (one image, multiple commands), `postgres`,
`minio`. MX + SPF/DKIM/DMARC DNS documented in `deploy/`.
`minio`. MX + SPF/DKIM/DMARC/DANE/MTA-STS DNS documented in `deploy/`.
## 3. Data model (Postgres)
Mirrors the resource shapes agent tooling already expects, so SDKs/MCP map cleanly.
Native and agent-shaped (not Mox's per-account bbolt index). Resource shapes mirror what agent tooling
expects so SDKs/MCP map cleanly.
```sql
pods (id, name, created_at) -- tenant isolation
inboxes (id, pod_id, address UNIQUE, display_name, client_id, metadata jsonb, created_at, updated_at)
inboxes (id, pod_id, address UNIQUE, display_name, dkim_selector, metadata jsonb, created_at, updated_at)
threads (id, inbox_id, subject, last_message_id, message_count, labels text[], updated_at, created_at)
messages (id, inbox_id, thread_id, message_id_hdr, in_reply_to, references text[],
from_addr, to_addrs text[], cc text[], bcc text[], subject,
preview, text, html, extracted_text, extracted_html,
raw_object_key, -- pointer to raw .eml in object store
labels text[], size_bytes, headers jsonb, ts tsvector, -- FTS
spf, dkim, dmarc, -- inbound auth verdicts (from mox pkgs)
junk_score, labels text[], size_bytes, headers jsonb, ts tsvector, -- FTS
created_at, updated_at)
attachments (id, message_id, filename, content_type, size_bytes, object_key, inline bool, content_id)
drafts (id, inbox_id, thread_id, to_addrs text[], cc, bcc, subject, text, html, send_at, client_id)
api_keys (id, pod_id, hash, scopes text[], created_at) -- bearer tokens, store only the hash
api_keys (id, pod_id, hash, scopes text[], created_at) -- bearer tokens; store only the hash
webhooks (id, pod_id, url, event_types text[], secret) -- HMAC-signed deliveries
outbox (id, message_id, status, attempts, last_error, next_attempt_at) -- send queue + retries
events (id, inbox_id, type, payload jsonb, created_at) -- audit + WS replay
domains (id, pod_id, name, dkim_privkey_ref, verified bool) -- per-domain DKIM keys + DNS state
```
Indexes: `inboxes(address)`, `messages(thread_id)`, `messages(inbox_id, created_at desc)`,
GIN on `messages.ts` (search) and `*.labels`. Raw MIME and attachments live in object storage, not
the DB — rows hold pointers.
GIN on `messages.ts` (search) and `*.labels`. Raw MIME + attachments live in object storage; rows
hold pointers.
## 4. Request / data flows
### Inbound (receive)
1. `smtpd` accepts on :25, enforces basic anti-abuse (rate limit, SPF check, size cap), and writes
the raw `.eml` to the object store.
2. Parser builds the MIME tree → extracts `text`/`html`, attachments, headers; computes
`extracted_text` (strip quoted history) and a `preview`.
3. Threading resolves `thread_id` from `In-Reply-To`/`References` → fallback to normalized-subject +
participants within a window.
4. Row inserted; `LISTEN/NOTIFY` fires → webhook + WebSocket `message.received` event.
1. `go-smtp` accepts on :25; anti-abuse gate via mox `iprev`/`dnsbl`/`ratelimit` + size cap; raw `.eml`
written to object store.
2. mox `spf`/`dkim`/`dmarc` verify the message; verdicts recorded. mox `message` builds the MIME tree
`text`/`html`, attachments, headers. mox `junk` scores spam. Quote-stripper computes
`extracted_text` + `preview`.
3. Threading resolves `thread_id` from `In-Reply-To`/`References`, falling back to normalized-subject
+ participants within a window.
4. Row inserted; `LISTEN/NOTIFY` fires → `message.received` webhook + WebSocket event.
### Outbound (send / reply)
1. API builds the RFC 5322 message (sets `In-Reply-To`/`References` for replies), persists a
`messages` row + `outbox` entry.
2. `sender` DKIM-signs and dispatches via the configured backend (self-host SMTP or relay API).
3. Bounces/complaints (SMTP DSN, or relay webhook) update `outbox.status` and emit events.
1. API builds the RFC 5322 message (sets `In-Reply-To`/`References` on replies); persists a `messages`
row + `outbox` entry.
2. `sender` mox-`dkim`-signs, then dispatches via the configured backend:
- **self-host:** mox `smtpclient` + `dane` + `mtasts` for authenticated, secure MX delivery.
- **relay:** SES/Postmark/Resend API.
3. Bounces/complaints (mox `dsn` parse, or relay webhook) update `outbox.status` and emit events.
### API surface (v1 — AgentMail-shaped)
```
@@ -116,56 +149,69 @@ POST /v0/inboxes/{id}/messages/send send
GET /v0/inboxes/{id}/messages list (limit, page_token, labels)
GET /v0/inboxes/{id}/messages/{id} get
POST /v0/inboxes/{id}/messages/{id}/reply
GET /v0/inboxes/{id}/threads /{id} list / get
GET /v0/inboxes/{id}/threads / /{id} list / get
…drafts, webhooks, search…
```
Bearer auth (`Authorization: Bearer`). Keeping paths/shapes close to AgentMail's public v0 means an
existing Go/Python/Node client (or MCP server) can target a self-hosted OpenMail by base-URL swap.
Bearer auth (`Authorization: Bearer`). Keeping paths/shapes close to AgentMail's public v0 lets an
existing client (or MCP server) target a self-hosted OpenMail by base-URL swap. Compatibility yields
to a cleaner native shape where they conflict.
### MCP server
A thin MCP front-end over the same core: tools `create_inbox`, `list_messages`, `get_thread`,
`send_message`, `reply`, `search` — so an agent (Claude Code, etc.) drives its own mailbox directly.
A thin MCP front-end over `core`: tools `create_inbox`, `list_messages`, `get_thread`, `send_message`,
`reply`, `search` — so an agent drives its own mailbox directly.
## 5. Deliverability plan (the part that actually matters)
## 5. Deliverability plan (the moat — and why embedding Mox helps most here)
- **v1: relay-by-default.** Send through SES/Postmark/Resend for inbox placement on day one; OpenMail
still owns inbound, storage, threading, API. Lowest-risk way to be useful immediately.
- **Self-host SMTP send (opt-in):** dedicated IP with PTR/rDNS, SPF (`v=spf1 ip4:… -all`), DKIM
signing (publish the public key), DMARC (`p=quarantine``p=reject`), gradual IP warmup, FBL
enrollment, and bounce/complaint suppression lists. Documented honestly as a multi-week reputation
effort, not a flag flip.
- **Inbound auth:** verify SPF/DKIM/DMARC on receipt; expose pass/fail in message metadata; optional
reject/quarantine policy per inbox.
- **Self-host send is achievable from day one for the protocol parts** because mox `smtpclient` +
`dane` + `mtasts` + `dkim` already implement authenticated, DANE/MTA-STS-secured MX delivery. What
remains is *operational*, not code: dedicated IP with PTR/rDNS, SPF (`v=spf1 ip4:… -all`), published
DKIM key, DMARC (`p=quarantine``p=reject`), gradual IP warmup, FBL enrollment, suppression lists.
- **Relay backend** (SES/Postmark/Resend) ships alongside for instant inbox placement while a
self-host IP warms up — chosen per-domain.
- **Inbound auth:** SPF/DKIM/DMARC verified on receipt (mox pkgs); verdicts exposed in message
metadata; optional reject/quarantine policy per inbox.
## 6. MVP milestones
1. **Core API + storage** — inboxes/messages/threads/drafts CRUD against Postgres + MinIO; bearer
auth; no real mail yet (seed via an ingest endpoint). *Proves the data model + API.*
2. **Inbound**`go-smtp` on :25, MIME parse, extraction, threading, `message.received` webhook.
1. **Core API + storage** — inboxes/messages/threads/drafts CRUD on Postgres + MinIO; bearer auth;
ingest endpoint to seed without real mail. *Proves the data model + API.*
2. **Inbound**`go-smtp` on :25 + mox verify/parse/junk + threading + `message.received` webhook.
MX a test domain at the VPS. *Proves receive.*
3. **Outbound via relay**`send`/`reply` through SES/Postmark with DKIM. *Proves the loop:
an agent receives and replies.*
3. **Outbound via relay**`send`/`reply`, mox-DKIM-signed, through a relay. *Proves the loop: an
agent receives and replies.*
4. **MCP server** — agent owns and operates an inbox end-to-end.
5. **Self-host SMTP send + deliverability hardening** — the long tail.
5. **Self-host SMTP send** — wire mox `smtpclient`/`dane`/`mtasts`; the deliverability/warmup long tail.
## 7. Repo layout (planned)
```
cmd/openmail/ # single binary: `serve`, `smtpd`, `sender`, `mcp` subcommands
internal/core/ # inbox/message/thread/draft services
internal/smtp/ # inbound server + MIME parse + extraction + threading
internal/sender/ # Sender interface + smtp & relay backends + DKIM
internal/smtp/ # go-smtp inbound server; calls mox verify/parse/junk + threading
internal/sender/ # Sender interface: smtp (mox smtpclient+dane+mtasts) & relay backends; mox dkim
internal/store/ # sqlc queries, object-store client
internal/api/ # chi HTTP handlers (v0 surface)
internal/mcp/ # MCP server over core
deploy/ # docker-compose.yml, Caddyfile, DNS (MX/SPF/DKIM/DMARC) notes
deploy/ # docker-compose.yml, Caddyfile, DNS (MX/SPF/DKIM/DMARC/DANE/MTA-STS) notes
db/migrations/ # SQL migrations
```
## 8. Open questions
## 8. Licensing & attribution
- Relay vs self-host SMTP as the *default* — relay is pragmatic, self-host is the point. Likely ship
relay default + self-host documented.
- How far to chase AgentMail API compatibility vs a cleaner native shape.
OpenMail is **MIT** (see `LICENSE`). Every dependency is permissive: Mox (MIT), `emersion/go-smtp`
and `go-message` (MIT), `pgx`/`chi` (MIT/BSD). We import Mox as a Go module (no vendored source in
this repo), but credit it prominently in the README; if we ever vendor Mox source we must retain its
MIT notice. **No GPL/AGPL** code anywhere — Maddy (GPL-3.0) and Stalwart (AGPL) are deliberately
avoided so the project stays MIT.
## 9. Open questions
- **mox `message` ⇄ go-smtp seam:** confirm mox's parser consumes a raw reader cleanly outside mox's
store (spike in milestone 2). If any Tier-1 package drags in `mox-`/`store` transitively, isolate
behind a thin adapter or vendor just that file.
- How far to chase AgentMail API compatibility vs. a cleaner native shape.
- Search: Postgres FTS is enough for v1; revisit (Meilisearch/Typesense) only if needed.
- Multi-tenancy depth — `pods` now, full RBAC later.
- Whether to expose an optional IMAP read path (via mox `imapserver` over our store would mean forking
it — more likely a thin standalone IMAP front-end later, kept out of v1).
```
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Karti Tripathi and the OpenMail contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+35 -24
View File
@@ -1,38 +1,49 @@
# OpenMail
A self-hosted, AI-native mailbox for agents — open-source infrastructure that gives an AI agent its
own real email inbox (receive, parse, thread, search, send) on a box you control.
**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.
Think "Gmail for agents, self-hosted." A single Go service + Postgres + object storage you run on a
VPS, exposing a clean REST API (and an MCP server) so any agent can own an address, read its mail as
structured threads, and reply.
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.
> **Status: private WIP.** This is a personal learning project and a self-hosting alternative in a
> space that hosted products (e.g. AgentMail) serve well. Keep it private. Don't reference it in any
> job application, PR, or interview, and shelve it if it would ever conflict with an employer. It was
> designed only from public API surfaces and public RFCs — nothing proprietary.
> **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.
See **[ARCHITECTURE.md](./ARCHITECTURE.md)** for the design.
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
The hard, valuable 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, IP reputation), parsing
MIME, threading, and storage at scale. OpenMail is an exercise in building that plumbing properly,
self-hostable, with an agent-first API on top.
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.
## 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.
## Goals
- **Self-hostable** in one `docker compose up` on a single VPS, scaling to a small fleet later.
- **Agent-first API** — persistent inboxes, structured threads, `extracted_text` (quoted-history
stripped), labels, search, drafts, webhooks/WebSocket events.
- **AgentMail-API-shaped** where reasonable, so existing agent tooling/MCP can point at a self-hosted
endpoint with minimal change. (Compatibility is a non-goal where it conflicts with a cleaner design.)
- **Deliverability taken seriously** — DKIM signing, SPF/DMARC guidance, and a pluggable send path
(self-host SMTP **or** relay through SES/Postmark) because reputation is the real moat.
- **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.
## Non-goals (for v1)
- A hosted multi-tenant SaaS. OpenMail is self-host-first.
- Beating a mature provider on deliverability out of the box — that takes IP warmup and time.
- A webmail UI. The product is the API + MCP; humans use their own client or the CLI.
- 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.