EmailFlare Inbox — Cloudflare Worker

Self-hosted email CRM inbox — deployed to the edge.

Every inbound email that hits your Cloudflare Email Routing rules lands in a clean contact thread with sequences, real-time push notifications, and a polished dashboard. No external service. No monthly seat fees. One command setup.

emailflare-inbox.workers.dev/inbox
JD
Jane Doe2m
Re: Getting started with your API
AM
Alex Miller1h
Thanks for the quick reply!
SC
Sam Chen3h
Question about rate limits...
TW
Taylor Wu1d
Welcome — trial started
JD
3 emails
InboundGetting started with your APIMon 09:14
Hi, I just signed up and I'm trying to figure out how to configure the webhook endpoint for...
SentRe: Getting started with your APIMon 09:32
Hi Jane, great question! You can configure the endpoint from the Settings page. Here's the exact...
InboundRe: Getting started with your APINow
That worked perfectly, thank you! One more thing — how do I add team members?
Reply to Jane…Send →
What's included

Everything your team needs, nothing you don't.

Inbox is a complete inbound email CRM — not a thin wrapper. Built specifically for Cloudflare Workers with D1, R2, Queues, and Durable Objects.

Contact threads

Inbound emails are automatically grouped into per-contact conversation threads. Gmail-style view with expandable email cards.

Inbound email capture

Cloudflare Email Routing delivers directly to your Worker via email() handler. Attachments stored in R2, full MIME parsing.

Email sequences

Schedule multi-step drip campaigns per contact. Powered by Cloudflare Queues — cron-triggered every 5 minutes, never misses a step.

Real-time notifications

New inbound emails trigger instant browser notifications via a persistent WebSocket connected to a Durable Object. No polling.

Multi-user with roles

Admin and member roles, invite-link onboarding. Secure session-based auth with login rate limiting via Workers KV.

Scoped API keys

Programmatic access for outbound replies and sequence management. Per-key domain scoping with Workers Rate Limiting (100 req/60s).

Architecture

How Inbox works

Five Cloudflare primitives working together — no external infrastructure, no servers to manage.

Cloudflare
1

Email Routing

Configure a catch-all or address-specific rule in your Cloudflare dashboard to forward inbound mail to the inbox Worker.

Worker
2

email() handler

The inbox Worker's email() handler fires for every inbound message. It parses MIME, stores the raw email and attachments in R2, and writes the contact + thread row to D1.

D1 + R2
3

Contacts & threads

D1 holds contacts, email metadata, users, inboxes, and sequence schedules. R2 stores raw email bodies and attachments with secure per-request signed URLs.

Dashboard
4

Inbox UI + Push

The React dashboard SPA is served directly from the Worker's Assets binding. On new email, a Durable Object fans out a WebSocket notification to all open sessions.

Queues + Cron
5

Sequences

A cron trigger runs every 5 minutes to enqueue due sequence steps. The queue consumer sends replies via the CF Email Sending API with deduplication and retry logic.

Deploy

One command to production.

The deploy script creates every Cloudflare resource, applies migrations, sets secrets, builds the dashboard, and deploys. Safe to re-run — idempotent throughout.

Serverless
Cloudflare Workers

No servers, no containers. D1 database + KV + R2 + Queue — all on the edge. One script from clone to live.

1
Copy the config template

Fill in your Cloudflare API token, account ID, and VAPID keys. Or leave everything blank to be prompted interactively.

cp scripts/config.example.toml scripts/config.toml
2
Generate VAPID keys

Inbox uses Web Push for real-time browser notifications. Generate a key pair and paste both keys into your config.

npx web-push generate-vapid-keys# paste PUBLIC_KEY and PRIVATE_KEY into config.toml
3
Run the deploy command

Creates D1, KV, R2, a Queue, applies all migrations, sets Worker secrets, builds the dashboard, and deploys.

just deploy-inbox
4
Open /setup and create your admin

Visit your Worker URL at /setup to create the first admin account. Then add inboxes, invite teammates, and configure Cloudflare Email Routing.

https://your-inbox.workers.dev/setup
Inbox Documentation

Set up, configure, and extend Inbox.

Prerequisites, step-by-step setup, Email Routing configuration, secrets reference, and the full Inbox API — everything in one place.

Prerequisites

#prerequisites

Inbox is a standalone Cloudflare Worker — it does not require the main EmailFlare Docker stack. You only need:

  • A Cloudflare account on a paid plan (Workers Paid, $5/month) — required for Queues and Durable Objects.
  • A domain on Cloudflare with Email Routing enabled.
  • A Cloudflare API token with Email Routing (Edit), DNS (Edit), and Zone (Read) permissions. See the CF Token guide →
  • Node.js 18+ and pnpm installed locally.
  • The emailflare repo cloned: git clone https://github.com/0xdps/emailflare
ℹ️Inbox shares the same emailflare D1 database as the main worker. If you already have the main worker deployed, the inbox deploy script will detect and reuse the existing database.

Step-by-step setup

#setup
1
Install dependencies — from the repo root run just install. This installs all workspace packages including the inbox Worker and dashboard.
2
Copy the config templatecp scripts/config.example.toml scripts/config.toml. Fill in [deploy] (your CF API token + account ID) and the [inbox] VAPID keys. All fields are optional — any blank value will be prompted interactively.
3
Generate VAPID keys — run npx web-push generate-vapid-keys and copy the public and private keys into config.toml under [inbox].
4
Run the deploy scriptjust deploy-inbox. The script creates all Cloudflare resources (D1, KV, R2, Queue), applies D1 migrations, sets all secrets, builds the dashboard SPA, and deploys the Worker.
5
Create your admin account — visit https://<your-worker>.workers.dev/setup (or your custom domain). Fill in admin name, email, and password. This endpoint is disabled after the first account is created.
6
Add inboxes — in the dashboard, go to Inboxes and create one per email address you want to receive mail on (e.g. [email protected]). Note the generated inbox address — you'll use it in the Email Routing rule.
7
Configure Email Routing — see the Email Routing section → for exact steps.

Cloudflare Email Routing

#email-routing

Inbox receives mail by registering the Worker as an Email Routing worker. You need to do this in the Cloudflare dashboard for each domain you want to receive mail on.

Add the routing rule

1
In the Cloudflare dashboard, go to Email → Email Routing → Routing Rules for your domain.
2
Click Create address. Set the action to Send to a Worker and choose emailflare-inbox from the worker list.
3
Set the matcher to a specific address (e.g. [email protected]) or use a catch-all rule to capture all mail to the domain.
4
Send a test email to the address and verify it appears in the Inbox dashboard under the correct contact thread.
💡The inbox address the Worker reads from is determined by the to header of the incoming email. Make sure the address matches one of your configured inboxes in the dashboard, or it will be filed under a default inbox.

Email Routing DNS records

Cloudflare automatically adds the required MX records when you enable Email Routing for a domain. If they're missing, visit Email → Email Routing → Overview and click Enable Email Routing. No manual DNS changes are needed.

config.toml reference

#config

Copy scripts/config.example.toml to scripts/config.toml. Any blank value causes the deploy script to prompt interactively. The file is git-ignored.

# scripts/config.toml[deploy]# D1 database name (default: "emailflare")database_name = "emailflare"# Cloudflare API token — used by wrangler during deploycloudflare_api_token = ""# Your Cloudflare account IDaccount_id = ""[secrets]# 32+ char random string for JWT signing: openssl rand -hex 32session_secret = ""# Runtime Cloudflare API token (Email Routing + DNS + Zone)cf_api_token = ""cf_account_id = ""[inbox]# Generate: npx web-push generate-vapid-keysvapid_public_key = ""vapid_private_key = ""

Secrets reference

#secrets

All secrets are set as Cloudflare Worker secrets via wrangler secret put. The deploy script handles this automatically; use just inbox-secret SECRET_NAME to update a single secret later.

SecretRequiredDescription
SESSION_SECRETrequired32+ character random string used to sign session JWTs. Generate with openssl rand -hex 32.
CF_API_TOKENrequiredCloudflare API token used at runtime for Email Sending (outbound replies). Needs Email Routing (Edit), Zone (Read), DNS (Edit).
CF_ACCOUNT_IDrequiredYour Cloudflare account ID. Used for Email Sending API calls.
VAPID_PUBLIC_KEYrequiredVAPID public key for Web Push. Generate with npx web-push generate-vapid-keys. Also stored in wrangler.jsonc as VAPID_PUBLIC_KEY var (served to the frontend).
VAPID_PRIVATE_KEYrequiredVAPID private key. Never exposed to the frontend. Keep this secret.

Updating a secret

# Interactive (prompts without echoing)just inbox-secret SESSION_SECRET# Or directly with wranglercd services/inbox-workerecho "new-value" | npx wrangler secret put SESSION_SECRET

Inbox API

#api

The inbox Worker exposes both a session-authenticated API (used by the dashboard) and an API-key-authenticated API (for programmatic access). All endpoints are prefixed with /api.

Authentication

Dashboard endpoints use a session cookie set on POST /api/auth/login. Programmatic endpoints accept a bearer token:

Authorization: Bearer eflive_xxxxxxxxxxxx

Auth endpoints

POST/api/auth/loginpublicSign in with email + password. Returns a session cookie.
POST/api/auth/logoutsessionInvalidate the current session.
GET/api/auth/mesessionReturn the current authenticated user.
POST/api/setuppublicCreate the first admin account. Disabled after first use.

People (contacts)

GET/api/peoplesessionList contacts. Supports ?search= and ?unread=true filters.
GET/api/people/:id/threadsessionFetch a contact's full email thread (inbound + sent, chronological).
POST/api/people/:id/readsessionMark all emails in a thread as read.
POST/api/people/:id/replysessionSend an outbound reply to a contact. Body: { from, subject, text, replyToMessageId? }

Inboxes

GET/api/inboxessessionList all configured inboxes for the account.
POST/api/inboxessessionCreate a new inbox. Body: { name, address }

Sequences

GET/api/sequencessessionList all sequences with step counts and active contact counts.
POST/api/sequencessessionCreate a new sequence. Body: { name, steps: [{subject, body, delayHours}] }
POST/api/sequences/:id/enrollsessionEnroll a contact in a sequence. Body: { personId, from }

Users & invites

GET/api/userssessionList all team members (admin only).
POST/api/invitesessionGenerate an invite link. Body: { role: "admin" | "member" }
POST/api/invite/:token/acceptpublicAccept an invite and create a new user account.
🔗All outbound sends (replies, sequences) go through the same Cloudflare Email Sending API used by the main emailflare worker. The CF_API_TOKEN secret must have Email Routing (Edit) permission.

Upgrading

#upgrading

Pull the latest code and re-deploy. The inbox-update just recipe runs migrations before deploying so schema changes are applied atomically.

# Pull latestgit pull origin trunkjust install# Apply any new migrations + rebuild dashboard + redeployjust inbox-update

What just inbox-update does

  • Runs wrangler d1 migrations apply emailflare --remote — applies any pending SQL migrations to the live database.
  • Rebuilds the inbox UI SPA (pnpm build in services/inbox-ui).
  • Runs wrangler deploy from services/inbox-worker — uploads new Worker code with the rebuilt assets.
Migrations are idempotent — already-applied migrations are skipped automatically. You can safely run just inbox-update at any time without risk of data loss.

Manual redeployment

# Rebuild dashboard onlyjust inbox-build-dashboard# Deploy without rebuilding dashboard (code-only change)cd services/inbox-worker && npx wrangler deploy# Update a single secretjust inbox-secret VAPID_PRIVATE_KEY