Files
openmail/website/index.html
T
Karti TripathiandClaude Opus 5 a42b798a0e Scaffold tier 2 and 3, the schema, the website, and self-hosted-first
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
2026-09-02 13:20:51 -07:00

223 lines
9.5 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OpenMail — agent-native mail you host yourself</title>
<meta name="description" content="An agent-native, self-hosted mail server in Rust. Apache-2.0. Give an AI agent a real email address on infrastructure you control.">
<meta property="og:title" content="OpenMail">
<meta property="og:description" content="Agent-native, self-hosted mail server. Rust, Apache-2.0.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://openmail.karti.ai">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✉️</text></svg>">
<style>
:root {
--bg:#0b0d10; --panel:#12151a; --line:#232830;
--fg:#e6e9ee; --dim:#98a2b3; --faint:#6b7480;
--accent:#7dd3a0; --warn:#e8b464;
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
}
@media (prefers-color-scheme: light) {
:root { --bg:#fbfbfa; --panel:#fff; --line:#e4e4e0; --fg:#16181d;
--dim:#5a626e; --faint:#8a919c; --accent:#1a7f4e; --warn:#8a5a10; }
}
* { box-sizing: border-box; }
body { margin:0; background:var(--bg); color:var(--fg); font-family:var(--sans);
line-height:1.6; -webkit-font-smoothing:antialiased; }
.wrap { max-width: 820px; margin:0 auto; padding: 0 24px; }
code, pre, .mono { font-family: var(--mono); }
header { padding: 88px 0 56px; border-bottom:1px solid var(--line); }
.logo { font-family:var(--mono); font-size:13px; letter-spacing:.14em;
text-transform:uppercase; color:var(--accent); margin-bottom:26px; }
h1 { font-size: clamp(30px, 5.4vw, 46px); line-height:1.12; margin:0 0 18px;
letter-spacing:-0.025em; font-weight:640; }
.lede { font-size:19px; color:var(--dim); max-width:60ch; margin:0 0 26px; }
.badges { display:flex; gap:10px; flex-wrap:wrap; font-family:var(--mono); font-size:12px; }
.badge { border:1px solid var(--line); border-radius:5px; padding:4px 10px; color:var(--dim); }
.badge.wip { color:var(--warn); border-color:color-mix(in srgb, var(--warn) 40%, var(--line)); }
section { padding: 52px 0; border-bottom:1px solid var(--line); }
h2 { font-size:13px; font-family:var(--mono); text-transform:uppercase;
letter-spacing:.13em; color:var(--faint); margin:0 0 24px; font-weight:500; }
h3 { font-size:17px; margin:0 0 6px; font-weight:600; letter-spacing:-0.01em; }
p { margin:0 0 16px; }
p.sub { color:var(--dim); }
a { color:var(--accent); text-decoration:none; border-bottom:1px solid transparent; }
a:hover { border-bottom-color: currentColor; }
.grid { display:grid; gap:20px; grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); }
.card { background:var(--panel); border:1px solid var(--line); border-radius:9px; padding:20px; }
.card p { margin:0; color:var(--dim); font-size:14.5px; }
pre { background:var(--panel); border:1px solid var(--line); border-radius:9px;
padding:18px; overflow-x:auto; font-size:13.5px; line-height:1.65; margin:0 0 16px; }
pre .c { color:var(--faint); }
table { width:100%; border-collapse:collapse; font-size:14.5px; }
th { text-align:left; font-family:var(--mono); font-size:11.5px; text-transform:uppercase;
letter-spacing:.1em; color:var(--faint); font-weight:500;
padding:0 12px 10px 0; border-bottom:1px solid var(--line); }
td { padding:11px 12px 11px 0; border-bottom:1px solid var(--line); vertical-align:top; }
td:first-child { font-family:var(--mono); font-size:13px; white-space:nowrap; }
td.note { color:var(--dim); }
.first { color:var(--accent); font-size:12.5px; font-family:var(--mono); }
.tablewrap { overflow-x:auto; }
.callout { background:var(--panel); border:1px solid var(--line);
border-left:3px solid var(--warn); border-radius:0 9px 9px 0;
padding:16px 20px; margin:0 0 16px; }
.callout p { margin:0; font-size:14.5px; color:var(--dim); }
footer { padding:44px 0 72px; color:var(--faint); font-size:13.5px; }
footer a { color:var(--dim); }
</style>
</head>
<body>
<header>
<div class="wrap">
<div class="logo">OpenMail</div>
<h1>Give an agent a real email address — on a box you own.</h1>
<p class="lede">
An agent-native, self-hosted mail server in Rust. Receive, parse, thread,
search and send real SMTP mail behind a REST API and an MCP server.
Agents and humans are both first-class users.
</p>
<div class="badges">
<span class="badge">Apache-2.0</span>
<span class="badge">Rust</span>
<span class="badge">Self-hosted</span>
<span class="badge wip">v0.1 — in development</span>
</div>
</div>
</header>
<section>
<div class="wrap">
<h2>The gap</h2>
<p>
The hard part of an agent mailbox was never the API. It is the plumbing:
receiving over SMTP, sending with real deliverability, parsing genuinely
broken MIME, threading, storage.
</p>
<p class="sub">
Hosted agent-mail products solve that well — closed, on someone else's
infrastructure. Self-hostable mail servers solve it too — with no notion
of an agent: no per-agent inbox provisioning, no threads as API
resources, no MCP.
</p>
<p><strong>OpenMail is the intersection nobody occupies:</strong> agent-native, self-hostable, permissively licensed.</p>
</div>
</section>
<section>
<div class="wrap">
<h2>What agent-native means</h2>
<div class="grid">
<div class="card">
<h3>Inboxes are API resources</h3>
<p>Provisioned in one call. Not Unix accounts, not aliases, not a mailbox someone creates by hand.</p>
</div>
<div class="card">
<h3>Threads, not folders</h3>
<p>Stitched from <code>In-Reply-To</code> and <code>References</code>. An agent asks for a conversation.</p>
</div>
<div class="card">
<h3>Quoted history stripped</h3>
<p><code>extracted_text</code> is the new content only. An agent reading full bodies re-reads the whole thread every turn.</p>
</div>
<div class="card">
<h3>MCP, natively</h3>
<p>An agent owns and operates its mailbox as tools. Credential routes can never become tools.</p>
</div>
<div class="card">
<h3>Push, not poll</h3>
<p>Webhooks and a WebSocket stream for <code>message.received</code>.</p>
</div>
<div class="card">
<h3>Humans too</h3>
<p>Standard IMAP/SMTP is a goal, not an afterthought. Point Apple Mail at the mailbox an agent is driving.</p>
</div>
</div>
</div>
</section>
<section>
<div class="wrap">
<h2>Built on crates that did not exist</h2>
<p class="sub">
Two implementations of mail plumbing exist under a permissive licence: Mox
(MIT, Go), and Stalwart's primitives (Apache-2.0, Rust). Stalwart's
<em>server</em> is AGPL — which is why no permissively licensed Rust mail
server exists.
</p>
<p class="sub">
Building one means writing what the ecosystem is missing. These ship
standalone so any Rust mail project can use them.
</p>
<div class="tablewrap">
<table>
<thead><tr><th>Crate</th><th>What</th><th>Prior art</th></tr></thead>
<tbody>
<tr><td>mail-dane</td><td class="note">DANE / TLSA for SMTP (RFC 7672)</td><td class="first">first in Rust</td></tr>
<tr><td>mail-mta-sts</td><td class="note">MTA-STS policy discovery and cache (RFC 8461)</td><td class="first">first in Rust</td></tr>
<tr><td>mail-dsn</td><td class="note">Delivery Status Notifications (RFC 3464)</td><td class="first">first in Rust</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section>
<div class="wrap">
<h2>Sending: rent reputation, or build your own</h2>
<pre><span class="c"># relay — someone else's IP reputation, inbox placement on day one</span>
provider = "ses" <span class="c"># or oracle, sendgrid, postmark, resend, custom</span>
region = "us-east-1"
<span class="c"># or direct-to-MX — your reputation, MTA-STS and DANE enforced</span>
mode = "direct"</pre>
<p class="sub">Receiving is always yours.</p>
<div class="callout">
<p>
<strong>Oracle Cloud blocks outbound TCP/25</strong> for tenancies created
after June 2021 — inbound is unaffected. There you receive directly and
relay outbound on 587. Direct-to-MX is not possible on that host at all.
</p>
</div>
</div>
</section>
<section>
<div class="wrap">
<h2>Self-hosted first</h2>
<p>
OpenMail is a server you run. Not a trial of a hosted product, not an
open-core teaser with the useful half behind a licence key.
</p>
<p class="sub">
Apache-2.0 means you can run it, fork it, and build a commercial product
on top — including one that competes with anything we might host later.
That is the intent, not an oversight.
</p>
<pre>git clone https://github.com/karti-ai/openmail
cd openmail && docker compose up</pre>
</div>
</section>
<footer>
<div class="wrap">
<p>
<a href="https://github.com/karti-ai/openmail">GitHub</a> ·
<a href="https://github.com/karti-ai/openmail/tree/main/docs/adr">Decision records</a> ·
<a href="https://github.com/karti-ai/openmail/blob/main/SECURITY.md">Security</a>
</p>
<p>Apache-2.0. Not affiliated with any similarly named project.</p>
</div>
</footer>
</body>
</html>