Skip to main content
Home/Case Studies/Building a Self-Hosted Webmail Client on Cloudflare's Edge
Cloud SolutionsOpen Source / Email

Building a Self-Hosted Webmail Client on Cloudflare's Edge

How we built orange-inbox — an open-source, Gmail-style webmail client that runs entirely on Cloudflare — giving Email Routing users a real inbox with no mail server, no origin infrastructure, and mail that never leaves their own account.

10+
Cloudflare Products
None
Mail Servers
None
Third-Party Vendors
Zero
Cold Starts

Overview

Cloudflare Email Routing is one of the quietest great deals in the Cloudflare catalog: point your domain's MX records at Cloudflare and inbound mail for any address on that domain is yours to do something with — for free. The catch is the "something." Out of the box, Email Routing only forwards. Anyone who owns a handful of domains and wants to actually read and reply to their mail has two unappealing options. Forward everything into a personal Gmail or Outlook account, and a third party now holds all your correspondence, every domain's mail lands in one undifferentiated pile, and replies go out under the wrong identity. Or stand up a traditional mail server — and inherit IMAP, spam filtering, TLS certificates, deliverability reputation, and a patching treadmill that never ends.

We wanted the third option: a real inbox. So we built orange-inbox — an open-source, Gmail-style webmail client that runs entirely on Cloudflare, with no mail server and no origin infrastructure of any kind. It receives, threads, reads, composes, and sends mail for as many domains as you own, presents them in a familiar three-pane UI, and installs to a phone as a Progressive Web App. The entire thing — inbound parsing, the database, raw message storage, outbound delivery, authentication, and the UI — is assembled from Cloudflare primitives and deploys into your own Cloudflare account with a single click.

The Challenges

Building a webmail client without a mail server means re-deriving, from edge primitives, every piece of infrastructure that a traditional mail stack hands you for free — receiving, storage, sending, and identity — while keeping the result fast enough and simple enough that one person can deploy it in an afternoon.

1

Receiving and Parsing Inbound Mail Without a Mail Server

A mail server's first job is to speak SMTP, accept a message, and turn raw MIME into something structured. We needed all of that without running a server that listens on port 25. Inbound mail arrives as a single opaque blob — nested MIME parts, multiple encodings, inline images, attachments, calendar invitations, and headers that range from informative to actively hostile. The platform had to parse that blob reliably, decide which mailbox it belongs to, stitch it into the right conversation thread, and — critically — decide whether the sender's claimed identity could be trusted. Authentication-Results headers carry the SPF, DKIM, and DMARC verdicts, but a message can contain forged Authentication-Results headers placed there by the sender. Trusting the wrong one turns a phishing email into a green checkmark.

2

Outbound Mail With Real Deliverability

Receiving is only half an inbox. Sending mail that actually reaches the recipient is the harder half. Modern mail providers reject or spam-bin anything that fails SPF, DKIM, or DMARC alignment, and getting those right traditionally means managing DNS records, signing keys, and a sending IP's reputation by hand. The platform needed to send replies and new messages from any of the user's domains, with correct authentication, without operating an SMTP relay or buying into a third-party email API that becomes another vendor, another bill, and another point of failure.

3

Authentication Without a Password Store

A webmail client guards a person's entire correspondence. That makes its login the single most security-sensitive surface in the product — and the most tedious to build well: password hashing, reset flows, session management, multi-factor enrollment, and the breach liability that comes with storing credentials at all. We did not want orange-inbox to own any of that. The challenge was to ship a multi-user application, with different roles per domain, that holds zero passwords in its database and still supports MFA and SSO.

4

Storing Mail That Outgrows a Single Database

Email is one of the few workloads that only ever grows. Cloudflare D1, the natural home for structured metadata, caps a single database at 10 GB. For a light account that is years of headroom; for a busy multi-domain deployment it is a ceiling you will eventually hit. A naive design would simply fail when the database filled. The platform needed a storage model that could grow past the limit of any one database — without fragmenting conversations across databases and without forcing a painful data migration when the day came.

5

Multi-Domain Mail as a First-Class Concern

Most webmail software assumes one organization and one domain. Real users own several — a company domain, a couple of side projects, a personal vanity domain — and want them in one place without blending them into mush. That raises questions a single-domain design never has to answer: where do you sign in if you own five domains? Can one person be an administrator of one domain and a read-only user of another? How do you offer both a unified inbox and clean per-domain separation at the same time? These had to be answered in the data model from the very first migration, not bolted on later.

The Solution

We designed orange-inbox as two cooperating Cloudflare Workers over a shared set of bindings — one handling inbound mail, one serving the application — with D1, R2, and KV providing storage and Cloudflare Access providing identity. No origin servers, no containers, no database connection pools.

1

Two Workers Sharing One Set of Bindings

The platform is two Workers. A standalone email-worker is the destination Cloudflare Email Routing dispatches every inbound message to; its entire job is the email() handler. A second Worker, the web app, is a Next.js 16 application deployed through the @opennextjs/cloudflare adapter — it serves the three-pane UI and the full REST API for reading and sending mail. Both Workers declare the same D1, R2, and KV bindings, so they operate on one shared state with no network protocol between them. Where they do need to talk — the email-worker handing a scheduled send to the web app — they use a Cloudflare service binding, a private Worker-to-Worker call that never touches the public internet, gated by a shared secret as defense in depth.

2

Inbound Mail: Email Routing to D1 and R2

When mail arrives, Cloudflare Email Routing invokes the email-worker's email() handler with the raw message stream. The worker parses the MIME tree with postal-mime, writes the untouched raw .eml bytes to an R2 bucket — the immutable source of truth, always available for export or re-parsing — extracts attachments into a second R2 bucket, and inserts structured thread and message rows into D1. Threading reconciles the References and In-Reply-To headers to attach each message to its conversation. For sender trust, the worker reads SPF, DKIM, and DMARC verdicts only from the Authentication-Results header stamped by the trusted receiver, pinned by a configured authserv-id — so a forged header embedded in the message body cannot fabricate a passing verdict. Inbound mail is also run through categorization and user-defined filtering rules at parse time, so the inbox is organized before the user ever opens it.

3

Outbound Mail Through the send_email Binding

Outbound mail uses Cloudflare's send_email Worker binding. The web Worker composes a MIME message and hands it to the binding; Cloudflare relays it, applying SPF, DKIM, and DMARC alignment for any domain that has Email Routing enabled on the account. There is no SMTP relay to operate, no signing keys to rotate, and no third-party email API in the path — deliverability becomes Cloudflare's managed responsibility. Replies are identity-aware: orange-inbox sends from the address the original mail was addressed to, so a message to support@ is answered by support@, not by whichever domain the user happens to be signed in under.

4

Authentication Delegated to Cloudflare Access

orange-inbox has no login form, no password column, and no session store. Authentication is delegated entirely to Cloudflare Access, which sits in front of the application. Access handles the identity provider — one-time PIN, Google, GitHub, SAML, or OIDC — along with MFA and hardware keys, and injects a signed Cf-Access-Authenticated-User-Email header on every authenticated request. The Worker trusts that header, verifies the accompanying signed JWT, and lazily creates a user row on first sign-in. Some routes must stay public — confidential-message recipients, calendar apps fetching subscribed ICS feeds, open-tracking pixels, and the app's own static assets — so every public route is consolidated under a single /p/* prefix. One Access bypass rule then exposes exactly those paths and nothing else, and each public route carries its own unguessable URL token as its credential.

5

Splitting Storage Across D1, R2, and KV

Each Cloudflare storage primitive does the job it is best at. D1 — SQLite at the edge, co-located with the Worker for sub-millisecond reads — holds all structured metadata: threads, messages, mailboxes, labels, contacts, templates, calendar events, rules, and the user and role tables. R2 holds the heavy, immutable bytes: raw .eml files and attachments, served to the browser through short-lived signed URLs so large payloads never stream through the Worker. KV backs auto-saved compose drafts, where its low-latency, eventually-consistent writes fit a field that is overwritten every time the user pauses typing. Nothing is stored outside the operator's own Cloudflare account.

6

Overflow Sharding for Mail That Outgrows D1

To get past D1's 10 GB-per-database ceiling, orange-inbox separates a primary "control" database from zero or more "mail" databases. The control database holds bounded data that never moves — users, mailboxes, drafts, contacts, templates, labels, and the registry of mail databases. Message and thread bodies live in the mail databases. Each thread is pinned at creation time to whichever mail database had capacity, and every reply routes back to that same database, so a conversation is never split across shards. Two soft levers govern each database: a soft cap (8 GB) past which no new threads route there, and a hard cap (9.5 GB) past which it accepts no writes — the 1.5 GB gap is the budget to expand before anything breaks. A live capacity bar in the sidebar tracks usage, and adding more storage is a single command that provisions new D1 databases, applies their schema, and registers them.

7

A Gmail-Style Inbox With Search, Scheduling, and Offline Support

The UI is a familiar three-pane webmail client — mailbox list, thread list, reading pane — with a unified inbox across every domain and per-domain views when separation matters. Full-text search is powered by SQLite's FTS5 inside D1, using an external-content index kept in sync by triggers, so search adds storage for postings only rather than a second copy of every message. Compose is a rich-text editor built on Lexical, with templates, per-mailbox signatures, scheduled send, and a brief undo-send window that holds outbound mail before dispatch. A one-minute Cron Trigger on the email-worker is the platform's heartbeat — it dispatches scheduled sends when their time arrives, fires calendar reminders, and sweeps expired temporary uploads. The whole app is a Progressive Web App: it installs to a phone's home screen, runs without browser chrome, and delivers web push notifications for new mail.

The Results

The Cloudflare-native architecture produced a webmail client that is genuinely serverless end to end — and, because it deploys into the operator's own account, one where they own their mail outright.

orange-inbox

A Real Inbox With No Mail Server to Run

delivers what Email Routing alone does not: a true inbox you can read, search, organize, and reply from. And it does so with nothing to operate. There is no SMTP daemon, no IMAP server, no spam-filter tuning, no TLS certificate renewal, and no patching schedule. Inbound parsing, storage, sending, and the UI all run as edge functions that scale to zero when idle and execute with negligible cold-start latency when invoked. The operational surface of a complete mail platform has been reduced to two Workers and a database.

Because

Mail That Never Leaves Your Cloudflare Account

orange-inbox deploys into the operator's own Cloudflare account, every message — raw bytes in R2, metadata in D1, drafts in KV — lives in infrastructure they control. There is no third-party webmail provider reading the mail, no external email API in the send path, and no analytics SDK loaded in the UI. For anyone who adopted Cloudflare Email Routing specifically to stop handing their correspondence to a large provider, this closes the loop: mail is received, stored, and sent without ever leaving Cloudflare.

The

Storage That Grows With One Command

control-plane and mail-database split turns D1's 10 GB limit from a hard wall into a speed bump. A fresh deployment runs on a single database with zero configuration. When it approaches capacity, the sidebar capacity bar shows it coming, and one command adds overflow databases — each roughly 8 GB of additional headroom. Because threads are pinned to a database at creation, this scaling never fragments a conversation and never requires a data migration; the system simply routes new threads to wherever there is room.

The

One-Click Deploy, Idempotent Updates

whole platform deploys to a new Cloudflare account from a single "Deploy to Cloudflare" button: it forks the repository, provisions the D1, R2, and KV resources, and deploys both Workers. From there a short Cloudflare-dashboard checklist — turn on Access for login, enable Email Routing for each mail domain — wires it together. Updates use the same idempotent setup script, which checks for existing resources before creating anything, applies any new migrations, and redeploys both Workers. There is no build pipeline to maintain and no server to provision, patch, or scale.

orange-inbox

10+ Cloudflare Products in One Application

runs on Cloudflare Workers, Workers Static Assets, D1, R2, Workers KV, Email Routing, Email Workers (the send_email binding), Cron Triggers, service bindings, Custom Domains, and Cloudflare Access — eleven distinct products and platform features composing a complete, multi-user mail application with no origin server anywhere in the design. It is a concrete demonstration that Cloudflare's developer platform can support not just APIs and static sites but a full, stateful, security-sensitive product class — webmail — that has historically demanded dedicated server infrastructure to deliver.

Tags

Cloudflare WorkersD1R2KVEmail RoutingCloudflare AccessOpenNextWebmailEdge ComputingOpen Source

Ready to Achieve Similar Results?

Let our team of experts help you solve your toughest challenges and achieve transformational results.