Architecture
The single-Worker runtime, logical module boundaries, storage, and event flows.
Simple Inbox is deployed as one Cloudflare Worker named simple-inbox-cf.
+-------------------------------+
browser ---- fetch() ->| |----> D1
Email Routing email() -> simple-inbox-cf |----> private R2
Cloudflare Cron --------> |----> Email Sending
+-------------------------------+
web -> API -> mail modules
(in process)The root entry exports fetch, email, and scheduled. Web, API, and mail remain separate source
packages, but they run in one isolate and use in-process adapters rather than Service Bindings.
Logical responsibilities
| Component | Owns |
|---|---|
| Root/web | Public routing, UI/SSR, docs, security headers, setup redirects, /api/v1 bridge |
| API | Setup, sessions, CSRF, scopes, mailbox authorization, contracts, orchestration |
| Inbound capture, MIME parsing, threading, forwarding, outbound delivery, retention | |
| D1 | Installation, users, mailboxes, projections, sessions, idempotency, message state, blacklists, tombstones |
| R2 | Canonical raw .eml objects under opaque keys |
The root router is the only HTTP edge. The API calls mail through a narrow in-memory fetcher that
accepts only the fixed https://mail.internal/internal/* surface. The public router never maps those
mail routes, so /internal/* is not externally reachable.
Because all bindings share one physical Worker, source-package APIs, explicit binding construction, repository ownership, authorization checks, and boundary tests enforce the logical trust zones.
Installation flow
Before setup, only setup and public service metadata are usable. Protected APIs return unavailable,
inbound messages are rejected, scheduled retention is idle, and UI navigation goes to /setup.
The setup request must be same-origin and HTTPS, is source-rate-limited, and requires the independent
SETUP_TOKEN. One transactional D1 batch creates the normalized owner, primary mailbox, only owner
membership, and singleton installation record. The record stores the verified public origin, mail
domain, and retention values. Exact replay is idempotent; conflicting or incomplete state fails
closed.
Browser request flow
The browser calls same-origin /api/v1/*. The root Worker forwards only reviewed headers into the
API app and regenerates a request ID. The API authenticates the caller, enforces the required scope
and mailbox membership, validates the contract, and invokes a repository or private mail adapter.
Raw-message and private attachment responses require mailbox authorization. Shared attachment responses authorize an unguessable download capability against D1 before reading R2. Private responses use no-store caching, and object keys never appear in browser DTOs.
Inbound and outbound mail
Inbound email() processing persists the canonical raw message in R2, projects bounded searchable
content into D1, associates or creates a thread, and then optionally forwards to the owner. Durable
capture precedes forwarding, so forwarding failure does not erase mail.
Outbound operations reserve idempotency before invoking Email Sending. Provider interruption or an
ambiguous response remains unknown; the application does not blindly retry and risk a duplicate.
R2 remains outside D1 transactions and has a separate repair/lifecycle path.
Retention
Mail and completed attachments are stored indefinitely, including Spam and Trash. Scheduled retention is disabled and the compatibility handler does not resume old deletion tombstones. Existing R2 expiration rules must be removed separately by the owner.
Webmail streams multipart file parts through the authenticated Worker API into private R2. The server verifies completion and ownership, then includes HTML and plain-text links in outgoing mail. Completed files reject further part uploads. No R2 credentials or bucket CORS setup are needed. Downloads stream through a public, unguessable capability link without login or time-based expiration. Links are redacted from logs.
Deployment boundary
The root Wrangler config declares one Worker, D1, R2, Email Sending, rate limiting; no retention cron is enabled. Wrangler provisions D1/R2 and the root deploy task builds, migrates, and uploads the generated Worker config. Email domain verification, R2 lifecycle, custom domains, and Email Routing remain manual Cloudflare Dashboard owner actions.
No repository command discovers, imports, switches, or deletes a legacy resource.
Everyday inbox organization
New catch-all aliases are captured under Other inbound without forwarding. Promote an alias to an inbox from its conversation, or choose + New inbox… in the mailbox dropdown. Turning off Show as an inbox hides the alias and disables forwarding without deleting mail.
Inbox, Starred, Sent, All Mail, Spam, and Trash are views over message state. Sent retains a conversation when someone replies. Opening any result shows the full thread in collapsible message cards. Archive removes inbox membership, while Trash supports restore. Read state changes only for expanded incoming messages. Reading a forwarded copy in another mail client does not sync back.
General settings provides appearance preferences and explicit spam blacklists for inbound aliases, sender addresses, and sender domains (including subdomains). The Spam action asks whether to block a sender address or its entire domain before moving the conversation. Matching future mail is retained in Spam without forwarding. Not spam restores existing mail without removing the rule. Spam/Trash have no automatic expiration. No keyword or AI classifier is enabled.
Mailbox settings beside the inbox selector contains sender identity, forwarding, alias visibility, and HTML preferences for the selected mailbox. General settings applies across inboxes.
Use Block mailbox in mailbox settings to blacklist its receiving address in one click. Blocked mailboxes are hidden from the dropdown; their mail remains in Other inbound. Existing messages are unchanged. Removing the rule restores the previous visibility preference. Future mail to that mailbox goes to Spam without forwarding. Show HTML beside Raw email previews one message without changing its mailbox default.
When creating or promoting an inbox, enter the forwarding address or leave it empty to disable forwarding.
Copies of sent mail
Copy sent mail to forwarding address is on by default in each mailbox's settings. Messages and replies sent from webmail privately Bcc the configured forwarding address, with the same attachment links. Copies stop when you turn the setting off, disable forwarding, or hide the mailbox. An address already in To, Cc, or Bcc receives no duplicate. This applies to future sends only.
Forwarded customer replies and webmail replies include threading headers to help your email client keep both sides of the conversation together. Copies arrive as delivered mail; this does not place messages directly into another provider's Sent folder.
Markdown and image previews
Compose and reply offer Write, Preview, and Plain text tabs. Format messages with headings, bold, italics, lists, quotes, code, links, and images. Raw HTML is displayed as text. Upload a file, then use Insert link or Insert image to place it at the cursor. Supported uploaded images include PNG, JPEG, GIF, WebP, AVIF, and BMP; other files remain download links.
Previews do not publish uploads. Links become available when the message is sent, including when delivery is uncertain. Anyone with a sent attachment link can access it. Email clients may block remote images until the recipient chooses to load them. Markdown formatting is removed from the plain-text version, while descriptive links and attachment download URLs remain.
Received HTML previews use readable font, padding, and image defaults while keeping the sender's explicit styles. This improves older retained mail too; no migration or rewrite is needed.