Architecture v3: pluggable MailBackend; competitive positioning; relay-first

- 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) <noreply@anthropic.com>
This commit is contained in:
karti-ai
2026-06-21 12:42:32 -07:00
parent 551739baa3
commit abce753ba8
2 changed files with 72 additions and 11 deletions
+66 -11
View File
@@ -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)