cloud-1 (phx) and cloud-2 (us-sanjose-1) are both already OCI aarch64
instances, so the open question in ADR 0005 was answerable directly rather
than by reading forum posts. Measured from cloud-2:
BLOCKED 25 gmail-smtp-in.l.google.com, aspmx.l.google.com
OPEN 587/465/2525 gmail, sendgrid, SES, OCI Email Delivery
The community reports claiming 587 and 2525 are also blocked are wrong.
Only 25 is blocked, exactly as Oracle documents, so relay works and the
launch host is viable. Inbound :25 reachability is still unverified — that
is a VCN security-list change, not a platform limit.
ROADMAP.md carries the goal, three success signals that are deliberately
not v0.1, a definition-of-ready checklist, and five standing rules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkyvfNJGTshJNE9FtwPLk7
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
Supersedes the Go + embed-Mox design. The Go tree is removed; its
architecture doc is preserved at docs/archive/ARCHITECTURE-go-embed-mox.md
because its competitive analysis and data model still hold.
Five decisions recorded as ADRs:
0001 Rust, not Go — accepting ~5,500 lines of protocol code that Mox
would have given us free, to get the first permissively licensed
Rust mail server. Costs stated plainly.
0002 Apache-2.0, not MIT or AGPL — patent grant, trademark, CLA-free
contribution. Public on GitHub; Gitea stays as the private fallback.
0003 Stalwart's primitive crates (Apache-2.0/MIT) yes; its AGPL server
crates never. DANE and MTA-STS sit on the AGPL side of that line,
which is why we write our own.
0004 Milestones, reordered: embedded inbound is required at launch.
0005 Oracle Cloud blocks outbound :25, so direct-to-MX is impossible on
the launch host. Split delivery is mandatory, not an on-ramp.
Twelve crates in three tiers. Tier 1 (mail-dane, mail-mta-sts, mail-dsn)
is standalone and publishable — no `dane` or `mta-sts` crate exists on
crates.io at all today.
openmail-relay ships the provider table as data, with SES and Oracle from
the start. Oracle's and Resend's SPF includes are deliberately None: a
guessed include turns the DNS check green against a mechanism the provider
does not honour, and mail still fails SPF silently.
cargo check/test/clippy/fmt all green; unsafe_code is forbidden workspace
wide; cargo-deny enforces the licence policy in CI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkyvfNJGTshJNE9FtwPLk7
Blockers in the ingest path (all funnel real mail in milestone 2):
- H1: coerce body bytes to valid UTF-8 (ToValidUTF8) — non-UTF-8/mid-rune cuts
no longer abort the ingest tx and drop the message.
- H2: EnsurePart's recoverable parse error is non-fatal — proceed with the
guaranteed-usable Part so messy real-world mail is stored, not rejected.
- H3: extractBodies descends into message/rfc822 (Part.Message via
SetMessageReaderAt) — forwarded/bounce bodies no longer lost.
- H4: GetMessage/GetThread/GetThreadMessages scoped to inbox_id — no cross-inbox
access; reply no longer a confused deputy.
Hardening:
- M1: /healthz no longer leaks DB error to unauthenticated callers.
- M2: all DB errors funnel through handleErr; malformed UUID -> 404, dup -> 409,
internal errors no longer echo the driver string.
- M3: index messages(inbox_id, message_id_hdr) for thread resolution.
- M4: pods UNIQUE(name) + ON CONFLICT (name) — no duplicate default pods.
- L1: skip empty-User/Host addresses (no literal "@").
- L2: skip attachment-disposition parts when picking the body.
- L3: case-insensitive, trimmed 'Re:' detection.
Verified e2e vs Postgres 16: latin1 body stored valid UTF-8; rfc822-only body
extracted; cross-inbox 404; malformed UUID 404; dup 409; threading regression OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>
Design of record for OpenMail — one Go binary (smtpd/core/sender/mcp) + Postgres
+ object storage on a VPS. Covers inbound SMTP/MIME/threading, outbound
deliverability (relay-default, self-host opt-in), data model, AgentMail-shaped
v0 API + MCP server, and an MVP milestone path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>