中文 | English
A lightweight, self-hosted business email built on Cloudflare Email Routing + Node.js. No mail server to maintain — Cloudflare handles inbound mail (free, unlimited) and Resend handles outbound. Stand up a multi-user, send-and-receive email for your team (name@yourdomain.com) in minutes.
As of v2.0.2 this is a complete multi-user business email: admins manage accounts centrally; each employee logs in with their own business address and sends/receives independently, with isolated visibility.
- 🚀 Zero mail-server ops — No SMTP/IMAP server to run. Inbound via Cloudflare Email Routing (free, unlimited), outbound via Resend (100 emails/day free tier). Eliminates ~99% of the usual ops burden.
- 👥 Multi-user business email — Every business address (e.g.
alice@yourdomain.com) is a separate login identity. Employees log in with their own address + password and see/act on only their own mail; admins get a global view. - 📨 Send & receive, unified
- Inbound: Cloudflare forwards everything to a unified inbox; the system pulls via IMAP and auto-dispatches to each account by recipient address.
- Outbound: Employees use "Compose" in the web UI to send as the business domain (@yourdomain.com) — clean From, SPF/DKIM pass, not flagged as spam, no "on behalf of".
- 🛡️ Spam-folder safety net — Automatically identifies and scans IMAP Junk/Trash folders (resolved dynamically via special-use flags, works with Gmail's Chinese-named folders like
[Gmail]/垃圾邮件), so forwarded mail misclassified as spam is never lost. - 🌐 China-friendly — Both inbound (IMAP) and outbound (Resend) can go through an HTTP/SOCKS proxy, so it runs reliably behind the GFW.
- 🗄️ Lightweight storage — Just SQLite, no MySQL/Redis. Parsed mail bodies go to the DB; attachments to disk.
- 🔧 Full web admin — Responsive dashboard: overview, account management, mail viewer, compose, activity log.
- Small teams / startups / solo devs who want
@yourdomain.commail but don't want to pay for Google Workspace / Microsoft 365 / Alibaba Mail - Anyone who needs self-hosted, data-under-your-control email without running a complex mail server
- Cost-sensitive setups happy to run on free tiers (Cloudflare free + Resend free)
Inbound Outbound
Sender ──► Cloudflare Email Routing ──► Unified inbox (Gmail etc.)
│
IMAP pull + dispatch by recipient
│
┌───────┴───────┐
▼ ▼
Employee A Employee B
Employee A composes ──► CloudflareMailServer ──► Resend API ──► Recipient
(sent as alice@yourdomain.com)
- Account = identity: every business address is both a mailbox and a login.
- Inbound dispatch: Cloudflare forwards all
*@yourdomain.comto one real inbox (e.g. Gmail); the system pulls via IMAP and routes each message to its account by theTofield. - Outbound unified: all outbound mail goes through Resend (with yourdomain.com verified), with each sender shown as their own
name@yourdomain.com.
- A domain hosted on Cloudflare, with Email Routing enabled
- A Cloudflare API Token (permission:
Email Routing Rules: Edit, scoped to the domain) - A real inbox (e.g. Gmail with an app password after enabling 2-Step Verification) as the unified mailbox
- For sending: a Resend account + verified sending domain + API Key (see Installation)
# 1. Clone & install
git clone https://github.com/chawuciren/cloudflare-mail-server.git
cd cloudflare-mail-server
npm install
# 2. Copy and fill in config
cp config.yaml.sample config.yaml # Windows: copy config.yaml.sample config.yaml
# Edit config.yaml: Cloudflare token/zoneId, Gmail app password, Resend key
# 3. Start (DB initializes automatically on first run)
npm startOpen http://localhost:3001 and log in with the admin account from config (default admin/admin — change it immediately).
Full setup, Cloudflare/Gmail/Resend configuration: see Installation.
- Admin logs in → Accounts → Create / Generate a business address (e.g.
alice@yourdomain.com). A Cloudflare forwarding rule is created automatically and an initial password is generated. - Hand the address + password to the employee.
- The employee logs in at the login page with
alice@yourdomain.com+ password and lands in their own mail view:- Sees only mail addressed to them
- Can "Compose" and send out as
alice@yourdomain.com - Cannot see other employees' mail and has no admin features
See Usage.
- Installation — Setup, Cloudflare/Gmail/Resend config, troubleshooting
- Configuration — Every
config.yamloption explained - Usage — Multi-user guide, features, workflows
- API — Auth, accounts, send/receive APIs
- Cloudflare Email Setup — Cloudflare Email Routing walkthrough
- Backend: Express.js, SQLite (sqlite3 + sqlite), EJS
- Auth: JWT (API layer) + Session (web layer), dual-auth system
- Inbound: ImapFlow (IMAP pull)
- Outbound: Resend (HTTPS API via axios)
- Parsing: mailparser
- Integrations: Cloudflare API (account/route management), Resend (sending)
- Config: YAML (js-yaml)
| Module | File | Responsibility |
|---|---|---|
| Entry | server.js |
Init, startup, connection lifecycle |
| Web routes | routes/ |
Admin UI, mail viewer, compose, login |
| API routes | api/ |
Accounts/mail/auth REST API (JWT) |
| Cloudflare | utils/cloudflare-email-router.js |
Email routing rule CRUD |
| Inbound | utils/mail-service.js |
IMAP pull, spam-folder scan, dispatch |
| Outbound | utils/mail-sender.js |
Resend sending |
| Data | utils/database.js |
SQLite ops, migration, account/mail CRUD |
| Auth | utils/jwt-helper.js, utils/login-limiter.js |
JWT sign/verify, brute-force lockout |
Main sections of config.yaml (full details in Configuration):
server— port, hostproxy— proxy (needed in China for Gmail/Resend)cloudflare— API Token, Zone ID, domains (multiple supported)email.imap— inbound IMAP (unified inbox)email.send— outbound (Resend): provider, apiKey, fromDomainadmin— admin accountjwt— JWT secret & expiry
- Keep config secret —
config.yamlholds tokens / app passwords / Resend keys. It's in.gitignore; never commit or expose it. - Least-privilege token — Cloudflare token gets only
Email Routing Rules: Edit, scoped to the domain. - App passwords — Use app passwords for Gmail etc., never the main password.
- Change defaults — Change the
admindefault password on first run and set a strong JWT secret. - HTTPS in production — Terminate SSL via Nginx/Caddy reverse proxy.
- Backups — Back up
data/database.sqliteand the attachments directory.
npm run dev # nodemon hot reloadPRs welcome. Fork → branch → commit → push → open a Pull Request.
MIT


