Email with an AI assistant built in. A hosted service at inboxies.app, open source so you can also run your own copy on Cloudflare Workers.
Inboxies is an email client for the web, iOS and Android. Most people use the hosted service at inboxies.app, with an address on inboxies.email or on their own domain. This repository is the code behind that service, under the Apache 2.0 license. You can read it, contribute to it, or deploy it to your own Cloudflare account and run it there.
Everything runs on Cloudflare. Incoming mail arrives through Email Routing and outgoing mail leaves through Email Service. Each mailbox lives in its own Durable Object with a SQLite database. Message bodies, raw MIME and attachments are kept in R2, and accounts in D1. The assistant is built with the Agents SDK and Workers AI.
- Web: sign up at inboxies.app.
- iOS and Android: the native apps in
ios/andandroid/connect to the hosted service. - MCP: connect an MCP client to
https://inboxies.app/mcp. It signs in with OAuth. You choose the scopes (readalways, plusdraft,sendormanage) and the mailboxes the client may use.
You don't need anything in this repository to use Inboxies. The rest of this README is for developers.
- Full email client: send and receive mail with a rich text composer, threaded replies and forwards, folders, full-text search and attachments.
- Per-mailbox isolation: each mailbox runs in its own Durable Object, with SQLite for metadata, snippets and the search index, and R2 for bodies, raw MIME and attachments.
- Built-in AI assistant: a side panel that can read, search and draft mail. It saves drafts. Sending is always your choice.
- MCP server: outside AI tools can work with your mailboxes over OAuth 2.1, with scopes and a mailbox list you approve.
- Accounts: passwords, passkeys, two-factor authentication, and Sign in with Apple or Google. Mailboxes can be shared with other people as owners or members.
- Native apps: SwiftUI for iOS and Jetpack Compose for Android, using the same Worker API.
- Frontend: React 19, React Router v7, Tailwind CSS, Zustand, TipTap,
@cloudflare/kumo - Backend: Hono, Cloudflare Workers, Durable Objects (SQLite), D1, R2, KV, Queues, Email Routing, Email Service
- AI assistant: Cloudflare Agents SDK (
AIChatAgent), AI SDK v6, Workers AI (@cf/moonshotai/kimi-k2.5),react-markdown+remark-gfm - Auth: Better Auth with users and sessions in D1; Cloudflare Access for operator routes under
/ops
You need Node.js, pnpm and a Cloudflare account. Workers AI and the send_email binding run against Cloudflare even in local dev, so sign in to Wrangler first.
pnpm install
pnpm wrangler login
cp .dev.vars.example .dev.vars
pnpm auth:migrate:local
pnpm devOpen http://localhost:5173 and create an account at /onboarding. Local dev keeps D1, KV and R2 data in .wrangler/state, apart from any deployed Worker.
Self-hosting is supported. You deploy the same Worker to your own Cloudflare account and receive mail on your own domain. wrangler.jsonc in this repository is set up for the hosted service: it names its resources, routes and domains. Before your first deploy, create your own resources and point the config at them.
There is no Deploy to Cloudflare button. The config's custom-domain routes and production values would make that flow fail or deploy a broken copy.
- A Cloudflare account with a domain on Cloudflare DNS
- Email Routing for that domain, to receive mail
- Email Service set up for that domain, to send mail
- Workers AI, for the assistant
- Node.js and pnpm
git clone https://github.com/aveer-dev/boxies.git
cd boxies
pnpm install
pnpm wrangler loginCreate these in your account, then put the new IDs in wrangler.jsonc:
| Resource | Command | In wrangler.jsonc |
|---|---|---|
| D1 database for accounts | pnpm wrangler d1 create inboxies-auth |
d1_databases[0].database_id (and database_name if you chose another name) |
| KV namespace for MCP OAuth | pnpm wrangler kv namespace create OAUTH_KV |
kv_namespaces[0].id |
| R2 bucket for mail | pnpm wrangler r2 bucket create agentic-inbox |
r2_buckets[0].bucket_name and preview_bucket_name if you chose another name |
| Queues for sending and delivery events | pnpm run ensure-queues |
Nothing to change. It creates outbound-send, outbound-send-dlq, email-sending-events and email-sending-events-dlq |
Durable Objects, Workers AI and the rate limiters need nothing created. Each rate limiter's namespace_id must be unique in your account, so change them if they clash with limiters you already have.
In wrangler.jsonc:
name: your Worker's name.routes: replaceinboxies.emailandinboxies.appwith your own hostname(s). You can also deleteroutesand setworkers_devtotrueto use aworkers.devaddress.vars.DOMAINS: the domain(s) you receive mail for, comma-separated. New addresses use the first one, unless you setMAIL_DOMAIN.vars.APP_BASE_URL: the URL the web app is served from, for examplehttps://mail.example.com. Sign-in, passkeys, MCP OAuth and links in emails all use it.vars.WEB_REDIRECT_HOSTS: hosts whose web pages should redirect toAPP_BASE_URL. Use""if you serve from one host.vars.APPLE_WEB_SERVICES_ID,APNS_TOPICandAPNS_SANDBOXare the hosted service's Apple identifiers. They do nothing until you add Apple secrets (step 5). Change them to your own when you do.
pnpm auth:migrate:remoteThis applies migrations/auth to the D1 database bound as AUTH_DB.
Put your secrets in a file outside the repository, as JSON or .env lines (NAME=value). BETTER_AUTH_SECRET is required. Without it nobody can sign in. Generate one with openssl rand -base64 32.
pnpm run build
pnpm wrangler deploy --secrets-file ../inboxies-secrets.envDelete the file afterwards. Secrets you leave out of a later --secrets-file deploy keep their values, so you only need to include the ones you are adding or changing. After that, pnpm run deploy deploys code changes.
Optional secrets (see .dev.vars.example and workers/types.ts):
| Secret | What it turns on |
|---|---|
POLICY_AUD, TEAM_DOMAIN |
Operator tools under /ops, behind Cloudflare Access. Unset leaves /ops closed. TEAM_DOMAIN may be the Access team URL or the full .../cdn-cgi/access/certs URL. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_EXTRA_CLIENT_IDS |
Sign in with Google |
APPLE_CLIENT_ID, APPLE_CLIENT_SECRET, APPLE_TEAM_ID, APPLE_SIGNIN_KEY_ID, APPLE_SIGNIN_PRIVATE_KEY |
Sign in with Apple |
APNS_KEY_ID, APNS_TEAM_ID, APNS_PRIVATE_KEY, FCM_SERVICE_ACCOUNT_JSON |
Push notifications for your own builds of the native apps |
CF_API_TOKEN, CF_ACCOUNT_ID, WORKER_NAME, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET |
The hosted service's sign-up with a custom domain, which sets up the domain's zone and Email Routing and takes payment. Not needed to run on your own domain. |
- Receive: in the Cloudflare dashboard, open your domain → Email Routing and add a catch-all rule that sends to this Worker.
- Send: set up your domain for Email Service so the Worker's
send_emailbinding can send from it. Sends, replies and forwards go through theoutbound-sendqueue. Messages that run out of retries go tooutbound-send-dlq, and the Sent message is marked failed. - Delivery events (recommended): open Queues → email-sending-events → Event subscriptions, choose Email Sending for your domain, and subscribe at least to
message.bouncedandmessage.complained(optionallymessage.failedandmessage.rejected). Subscribe tomessage.deliveredandmessage.deferredtoo for per-recipient status. Bounces and complaints then show on the Sent message in the thread. Events that fail 5 times go toemail-sending-events-dlq, are kept in R2, and can be listed, replayed or discarded under/ops/dead-letters/email-sending-events.
Open APP_BASE_URL and sign up at /onboarding for an address on your domain. Anyone who can reach your Worker can sign up the same way.
Outbound messages larger than 5 MiB (body and attachments) are rejected with HTTP 413 before sending. Mailbox send rate limits return HTTP 429.
Users, sessions, passkeys and two-factor settings live in D1 through Better Auth (workers/auth/). Each mailbox has members with a role, owner or member, and every request is checked against that role (workers/auth/authz.ts). Owners can share a mailbox with other people. MCP (/mcp) and the assistant (/agents/*) go through the same checks.
Operators (people running the deployment) are a separate group. They sign in to /ops through Cloudflare Access to manage domains and mailboxes, read the audit log, replay dead-lettered events and see spam metrics (workers/routes/ops.ts).
MCP clients authenticate to /mcp with OAuth 2.1: dynamic client registration and PKCE S256. Discovery is at /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server on APP_BASE_URL. A signed-in user gives consent at /authorize and picks the scopes (read, always granted, plus draft / send / manage) and the mailboxes the client may use. Clients, grants and tokens are stored in the OAUTH_KV namespace. Without that binding OAuth is off, and /mcp accepts only a session token sent as Authorization: Bearer, which is being deprecated. See workers/mcp/oauth.ts.
In local dev (pnpm dev), OAUTH_KV is a local namespace under .wrangler/state that starts empty, so register and authorize an MCP client against your local server. On the hosted service, MCP is at https://inboxies.app/mcp.
┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Browser │────>│ Hono Worker │────>│ MailboxDO │
│ React SPA │ │ (API + SSR) │ │ SQLite meta + │
│ Agent Panel │ │ │ │ snippets + FTS │
└──────┬───────┘ │ /agents/* ──────┼────>└────────┬────────┘
│ │ │ │
│ WebSocket │ │ ▼
└─────────────┤ │ ┌─────────────────┐
│ │ │ R2 │
│ │ │ body.html │
│ │ │ raw.eml │
│ │ │ attachments │
│ │ └─────────────────┘
│ │────>┌─────────────────┐
│ │ │ EmailAgent DO │
│ │ │ (AIChatAgent) │
└──────────────────┘ └─────────────────┘
The Worker also uses D1 (AUTH_DB) for accounts, mailbox membership and domains; KV (OAUTH_KV) for MCP OAuth; the EmailMCP Durable Object for the MCP server; the ExportJobDO Durable Object for mailbox exports; and Queues for outbound sends and Email Sending events. An hourly cron does upkeep (workers/lib/maintenance.ts).
Full HTML and raw MIME are stored in R2 (emails/{id}/body.html, emails/{id}/raw.eml). The mailbox's SQLite database keeps metadata and a short snippet for list previews, plus an FTS5 index (emails_fts) over subject, sender, recipients and plain-text body, so search matches the whole message and not just its first 300 characters. Deleting a mailbox purges its Durable Object, its R2 bodies and attachments, and its assistant chat history.
Apache 2.0. See LICENSE.
Inboxies started as a fork of Agentic Inbox by Cloudflare, Inc., also licensed under Apache 2.0. Files from that project keep their Cloudflare copyright headers, and the copyright notice in LICENSE is Cloudflare's. You can read more about the original project in Cloudflare's Email for Agents blog post.