From aab2a8558e37fe834135cab05b60a8e15e468b73 Mon Sep 17 00:00:00 2001 From: karti-ai Date: Sun, 21 Jun 2026 12:07:37 -0700 Subject: [PATCH] Architecture v2: embed Mox (MIT) as a library; native Postgres agent layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- ARCHITECTURE.md | 232 +++++++++++++++++++++++++++++------------------- LICENSE | 21 +++++ README.md | 59 +++++++----- 3 files changed, 195 insertions(+), 117 deletions(-) create mode 100644 LICENSE diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1660f1f..8f34fd9 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,171 +1,217 @@ # 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) - ┌───────────────▼──────────────────────────────┐ - │ core (Go) │ - Agents / SDKs ──REST──▶ • inbox/message/thread/draft services │ - MCP clients ───MCP──▶ │ • threading, extraction, labels, search │ - │ • auth (API keys, pods), webhooks/WS │ - └──────┬───────────────────────┬───────────────┘ - │ │ - ┌────────────▼─────┐ ┌───────────▼───────────────┐ - │ Postgres │ │ Object store (S3/MinIO) │ - │ metadata + FTS │ │ raw MIME + attachments │ - └──────────────────┘ └────────────────────────────┘ + ┌──────────────────────────────────────────────────────┐ + 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, extracted_text, labels, FTS search │ + │ • auth (API keys, pods), webhooks/WS │ + └──────┬───────────────────────────────┬────────────────┘ + │ │ + ┌────────────▼─────┐ ┌─────────────▼──────────────┐ + │ Postgres │ │ Object store (S3/MinIO) │ + │ 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) ``` POST /v0/inboxes create inbox -GET /v0/inboxes / /v0/inboxes/{id} list / get +GET /v0/inboxes / /v0/inboxes/{id} list / get 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). +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ac8c226 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index 31817d4..e6b10b5 100644 --- a/README.md +++ b/README.md @@ -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.