Agentic Inbox
Self-hosted AI-powered email client on Cloudflare Workers, running across my domains (lakebbs.ca + tensorproxies.com). Email Routing + Durable Objects + R2 + Workers AI + Agents SDK.
Context
I run two domains that need real email — lakebbs.ca (the community platform) and tensorproxies.com (the proxy infrastructure product). Both need to receive support / contact mail, both want light AI-assisted triage so I’m not sinking an hour a day into the inbox.
Agentic Inbox is a forked + extended deployment of Cloudflare’s agentic-inbox reference — a self-hosted email client entirely on Cloudflare Workers, with an AI agent that reads conversations and drafts replies. Running it for myself rather than handing email to a third-party service.
Architecture
The whole product runs on Cloudflare. No external SMTP, no third-party AI provider, no separate database. Everything terminates inside one account:
Browser (React SPA + Agent panel)
│
▼
Hono Worker (API + SSR)
│
├──▶ MailboxDO ──── SQLite (per-mailbox) + R2 (attachments)
│
└──▶ EmailAgent DO (AIChatAgent) ──── Workers AI tool calls
├── read inbox
├── search threads
├── draft replies
└── send (always with confirmation)
Why this stack actually works:
- Per-mailbox Durable Object isolation. Each address (
hello@tensorproxies.com,contact@lakebbs.ca, etc.) runs in its own DO with its own SQLite DB. No noisy-neighbor problem, no cross-mailbox query risk. - Email Routing for ingress. A catch-all rule on each domain pipes inbound mail straight into the Worker. The Worker resolves the recipient → mailbox DO → write to that DO’s SQLite. Inbound latency is sub-second.
- R2 for attachments. Anything bigger than a few KB lives in R2 keyed by the message ID. The SQLite row carries the reference, not the blob.
- Agents SDK for the assistant. The
AIChatAgentclass with 9 email tools (read, search, draft, send, …). The agent only DRAFTS — sending always requires my explicit confirmation in the UI. Auto-fire-and-forget is not a thing in this product on purpose.
What I Extended
The reference repo is solid but assumes a single domain. I needed it to multi-tenant across mine, plus some quality-of-life fixes:
- Multi-domain
DOMAINSconfig —wrangler.jsoncset tolakebbs.ca,tensorproxies.comso the catch-all routes from both domains land in the same Worker. - Email templates — pre-fill common reply shapes (TCF inquiry, proxy plan questions, ModBox press) so the agent’s draft is closer to my voice on first try.
- Sender avatars — small UI nicety; thread lists feel scannable instead of a wall of subject lines.
- Per-thread inline composing — reply / forward open in-thread rather than full-screen modal, which is what I’d actually want when triaging 20 messages back-to-back.
Security Model
The whole app sits behind Cloudflare Access. There’s exactly one trust boundary: any user who passes the shared Access policy can access all mailboxes (and the /mcp MCP server, which lets external AI tools like Claude Code operate on any mailbox by passing a mailboxId). I’m the only one in the Access policy, so this is fine — but I’d build per-mailbox authorization before opening it up to anyone else.
POLICY_AUD and TEAM_DOMAIN are Worker secrets, set via wrangler secret put. JWT validation on every request. The Worker enforces Cloudflare Access must be configured in production and refuses to boot otherwise — the kind of fail-loud default I want for anything touching my mail.
Tech Stack & Links
Frontend: React 19 · React Router v7 · Tailwind · Zustand · TipTap · @cloudflare/kumo
Backend: Hono · Cloudflare Workers · Durable Objects (SQLite) · R2 · Email Routing
AI: Cloudflare Agents SDK (AIChatAgent) · AI SDK v6 · Workers AI (@cf/moonshotai/kimi-k2.5) · react-markdown + remark-gfm
Auth: Cloudflare Access JWT validation (required outside local dev)
Upstream: github.com/cloudflare/agentic-inbox — Apache 2.0