From abce753ba8ee0cb731dc687e2a35e04d576ff7c5 Mon Sep 17 00:00:00 2001 From: karti-ai Date: Sun, 21 Jun 2026 12:42:32 -0700 Subject: [PATCH] Architecture v3: pluggable MailBackend; competitive positioning; relay-first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Confirm stack: Go, single binary, embed Mox + pluggable backend (chosen after landscape research). - Add MailBackend interface: relay (v1 default) / imap_smtp (BYO mailbox) / embedded (go-smtp + mox smtpclient/dane/mtasts, flagship). Deliverability becomes opt-in; useful on day 1. - Add competitive positioning (§0.1): closest competitor agenticmail is TS + Stalwart(AGPL) Docker sidecar; OpenMail differentiates as single binary, all-MIT, in-process. agentic-inbox is Cloudflare-locked. - Reorder milestones backend-first: relay loop before embedded SMTP. - State scope: self-hostable app, not a SaaS — no billing/Stripe. - README: pluggable backends + positioning. Repo layout: internal/mail/*. Co-Authored-By: Claude Opus 4.8 (1M context) --- ARCHITECTURE.md | 77 ++++++++++++++++++++++++++++++++++++++++++------- README.md | 6 ++++ 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e0235a8..ed08c6f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -3,8 +3,13 @@ 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. +store) with a REST API and MCP server on top. Mail I/O sits behind a pluggable **`MailBackend`** +interface so OpenMail is useful on day one against a relay or an existing mailbox, while embedded-Mox +self-host is the flagship, in-process path. This document is the design of record. It favors an MVP +that actually receives and sends real mail over a perfect feature set. + +> **Scope:** OpenMail is a **self-hostable app, not a SaaS** — no billing/payments, no Stripe, no +> hosted control plane. Everything runs on the operator's hardware. ## 0. The decision: embed Mox, don't rebuild and don't just wrap it @@ -45,6 +50,49 @@ clean. We embed the latter and replace the former with our own Postgres-backed s > 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. +## 0.1 Competitive positioning (why OpenMail exists) + +The agent-mailbox concept is already taken; the *implementation niche* is open. + +| Project | Lang | License | How mail runs | Gap OpenMail fills | +|---|---|---|---|---| +| **agenticmail** (closest competitor) | TypeScript | MIT | TS orchestrator + **Stalwart in a Docker sidecar**, SQLite | Multi-container, AGPL mail engine, SQLite — vs. **single Go binary, all-MIT, in-process, Postgres** | +| cloudflare/agentic-inbox | TS | Apache-2 | **Locked to Cloudflare Workers** + Email Routing | Vendor-neutral, runs anywhere | +| Mox / Maddy / Stalwart | Go/Rust | MIT/GPL/AGPL | Full mail servers | Not agent-native (no agent API/MCP, no threads/extract) | + +OpenMail's three differentiators, all downstream of the Go + embed-Mox choice: +1. **Single static binary** (vs. sidecar/multi-container). +2. **Fully MIT stack** — Mox is MIT, vs. agenticmail's AGPL Stalwart — so others can build commercial agents on top. +3. **In-process mail engine** — nobody else does this; it's only possible because Mox is importable Go. + +## 0.2 The `MailBackend` interface (pluggable mail I/O) + +All inbound delivery and outbound sending sit behind one interface, so the agent layer (API, MCP, +store, threading) never depends on *how* mail moves. Three backends, shipped in order of effort: + +```go +// MailBackend abstracts where mail comes from and how it leaves. The core never +// knows which implementation is active. +type MailBackend interface { + // Send dispatches an already-built, DKIM-signable RFC 5322 message. + Send(ctx context.Context, msg *OutgoingMessage) (SendResult, error) + // Start begins delivering inbound messages to the sink until ctx is cancelled. + // (relay/imap: poll or webhook; embedded: go-smtp on :25.) + Start(ctx context.Context, sink InboundSink) error + Capabilities() Caps // self-host? inbound-push? custom-domain? throwaway-addrs? +} +``` + +| Backend | Inbound | Outbound | Ops burden | Ships | +|---|---|---|---|---| +| **relay** | provider webhook / poll | SES / Postmark / Resend API (mox `dkim` sign) | lowest | **v1 default** | +| **imap_smtp** | IMAP IDLE on a BYO mailbox | SMTP submission to BYO server | low | v1 | +| **embedded** (flagship) | `go-smtp` :25 + mox verify/parse/junk | mox `smtpclient` + `dane` + `mtasts` (MX) | highest | milestone 5 | + +This makes the deliverability slog *opt-in*: a user gets a working agent mailbox immediately via +relay or their existing mailbox, and graduates to fully self-hosted SMTP only when they want to own +the whole stack. The `embedded` backend is the differentiator; the other two are the on-ramp. + ## 1. Component overview ``` @@ -173,22 +221,29 @@ A thin MCP front-end over `core`: tools `create_inbox`, `list_messages`, `get_th ## 6. MVP milestones +Backend-first ordering: get an end-to-end loop on the *lowest-ops* backend, then add the differentiator. + 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`, 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** — wire mox `smtpclient`/`dane`/`mtasts`; the deliverability/warmup long tail. + ingest endpoint to seed without real mail. *Proves the data model + API.* `MailBackend` interface + defined; a `null`/ingest backend satisfies it. +2. **Relay backend (loop closed)** — `imap_smtp` and/or `relay` backend: inbound via IMAP IDLE or + provider webhook → mox `message` parse + threading + `message.received`; outbound via SMTP + submission / relay API, mox-DKIM-signed. *Proves receive+reply against real mail with near-zero ops.* +3. **MCP server** — agent owns and operates an inbox end-to-end (tools over the core). +4. **Embedded inbound** — `go-smtp` on :25 + mox verify/parse/junk + anti-abuse; MX a test domain. + *Proves OpenMail can receive directly.* +5. **Embedded outbound + deliverability** — mox `smtpclient`/`dane`/`mtasts`; the flagship self-host + path and the IP-warmup/reputation long tail. ## 7. Repo layout (planned) ``` cmd/openmail/ # single binary: `serve`, `smtpd`, `sender`, `mcp` subcommands internal/core/ # inbox/message/thread/draft services -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/mail/ # MailBackend interface + shared parse/thread/DKIM helpers (mox pkgs) +internal/mail/relay/ # relay backend (SES/Postmark/Resend) +internal/mail/imapsmtp/ # imap_smtp backend (BYO mailbox: IMAP IDLE in, SMTP submit out) +internal/mail/embedded/ # embedded backend (go-smtp :25 in; mox smtpclient+dane+mtasts out) internal/store/ # pgx/sqlc queries, object-store client, embedded migrations internal/store/migrations/ # SQL migrations (go:embed'd into the binary) internal/api/ # chi HTTP handlers (v0 surface) diff --git a/README.md b/README.md index e6b10b5..adcfa91 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ internals of [Mox](https://github.com/mjl-/mox) (also MIT) for the hard, correct 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. +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.** + > **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.