{"slug": "show-hn-mailflare-self-hosted-email-custom-domain-with-cloudflare", "title": "Show HN: Mailflare – self-hosted email, custom domain with Cloudflare", "summary": "Mailflare, a self-hosted email platform, now allows users to manage custom domains through Cloudflare's API for inbound routing and sending. The open-source tool provides an AI-powered inbox with features like message summaries, urgency scoring, and automated reply drafting, while requiring a Cloudflare token with specific permissions for domain setup. The project aims to give users full control over their email infrastructure without relying on third-party dashboard interfaces.", "body_md": "A self-hosted, AI-powered email inbox with custom domains, powered by Cloudflare\n\n- Domain onboarding through Cloudflare, including inbound Email Routing DNS and sending DNS setup.\n- Domain removal cleanup for linked Cloudflare routing rules and sending subdomain resources.\n- Mailbox creation with automatic Cloudflare Email Routing rules.\n- Mailbox management with a grid view, mailbox detail page, and editable display name.\n- Inbox, sent, drafts, spam, and trash folders backed by a shared mail list component.\n- Popup composer with autosaved drafts and draft resume from the drafts folder.\n- Outbound send API, API keys, message read status, spam/trash moves, and seeded demo data.\n- Search, filtering, and richer mailbox/folder counts.\n- Advanced routing rules for catch-all addresses, forwarding, reject/block rules, and priorities.\n- Webhook management UI and delivery retry visibility.\n- Attachment support and richer compose formatting.\n\n- Message intelligence with summaries, intent classification, urgency scoring, and extracted entities.\n- Agent task queue for proposed replies, follow-ups, triage actions, and missing-information requests.\n- Human-approved actions for draft replies, folder moves, forwarding, contact creation, and webhook calls.\n- Agent rules for learned post-receipt policies such as prioritization, auto-triage, and reply templates.\n- Agent inbox view organized by action state, including needs reply, waiting on me, waiting on them, FYI, auto-handled, and needs approval.\n- Thread and contact memory for prior summaries, user preferences, relationship notes, commitments, and open loops.\n- Tool execution for trusted actions such as sending email, creating drafts, updating message status, calling webhooks, and creating contacts.\n\nDomains are **not** dashboard-only. This app calls Cloudflare when you add/remove a domain:\n\n| Action | Cloudflare API |\n|---|---|\n| List DNS / status | `GET /zones/{zone_id}/email/routing/dns` |\n| Enable inbound routing + MX/SPF/DKIM | `POST /zones/{zone_id}/email/routing/dns` |\n| Disable routing | `DELETE /zones/{zone_id}/email/routing/dns` |\n| Enable subdomain sending + DNS | `POST /zones/{zone_id}/email/sending/subdomains` |\n| Remove subdomain sending | `DELETE /zones/{zone_id}/email/sending/subdomains/{tag}` |\n| Subdomain sending DNS records | `GET .../subdomains/{tag}/dns` |\n\n**Requirements:** Prefer `CF_TOKEN`\n\nwith Zone Read + Email Routing Edit + Email Sending Edit + Email Routing Rules Write (or broader). If you use a legacy Global API Key instead, set `CF_API_KEY`\n\nand `CF_EMAIL`\n\n. The hostname must be the account's Cloudflare zone apex or a subdomain under that zone. Root-domain sending uses the Cloudflare Email Service binding, while subdomain sending can also provision the sending-subdomain DNS records. Mailbox creation creates a Cloudflare Email Routing rule that sends that address to `CF_EMAIL_WORKER_NAME`\n\n(`mailflare`\n\nby default).\n\nApp routes:\n\n`GET/POST /api/domains`\n\n— list / add (calls Cloudflare)`GET/DELETE /api/domains/[id]`\n\n— get / remove (disables routing & sending on CF)`GET /api/domains/[id]/dns`\n\n— routing + sending DNS snapshot\n\n```\ncp .dev.vars.example .dev.vars\n# Add CF_TOKEN and optionally CF_ACCOUNT_ID.\n# For a legacy Global API Key, use CF_API_KEY + CF_EMAIL instead.\n\nnpm install\nnpm run db:migrate:local\nnpm run dev\n```\n\nRegister at `/register`\n\n, complete `/onboarding`\n\n, or seed dev data:\n\n```\ncurl -X POST http://localhost:3000/api/seed\n```\n\nPublish this repository to GitHub, then replace `hieunc229/mailflare`\n\nin the button at the top of this README with the public repository path.\n\nThe deploy flow reads `wrangler.jsonc`\n\n, provisions the Worker bindings, prompts for values from `.dev.vars.example`\n\n, runs D1 migrations, builds the OpenNext Worker, and deploys it.\n\nKeep `wrangler.jsonc`\n\ncommitted. Cloudflare's deploy button uses it to detect the Worker entrypoint and required bindings. Do not commit `.dev.vars`\n\n; deploy-time secrets should be entered through Cloudflare's setup flow or set locally in `.dev.vars`\n\n.\n\nRequired setup values:\n\n`CF_TOKEN`\n\n— runtime scoped Cloudflare API token with Zone Read, Email Routing Edit, Email Sending Edit, and Email Routing Rules Write. This is separate from Cloudflare's deploy/build token; Cloudflare does not automatically expose the deploy token to this app.`CF_ACCOUNT_ID`\n\n— optional unless your token can access multiple accounts.`CF_EMAIL_WORKER_NAME`\n\n— must match the Worker name in`wrangler.jsonc`\n\n; default is`mailflare`\n\n.\n\nIf you rename the Worker, also update related literal resource names in `wrangler.jsonc`\n\n: `name`\n\n, `services[].service`\n\nfor `WORKER_SELF_REFERENCE`\n\n, `CF_EMAIL_WORKER_NAME`\n\n, and any D1/R2/Queue names you want renamed. Cloudflare service bindings require the target Worker name to exist exactly; they cannot currently reference `name`\n\ndynamically.\n\nAfter deployment, route inbound mail to the Worker in Cloudflare Email Routing.\n\nIf onboarding fails with `Cloudflare API 403 ... code 9109: Invalid access token`\n\n, Cloudflare rejected the credential before checking domain permissions.\n\nThe Deploy to Cloudflare flow can authenticate and deploy the Worker, but it does not create a runtime `CF_TOKEN`\n\nfor Mailflare's onboarding API calls. Create `CF_TOKEN`\n\nmanually from Cloudflare dashboard user API tokens and enter it as a deploy secret/variable.\n\nVerify the token:\n\n```\ncurl \"https://api.cloudflare.com/client/v4/user/tokens/verify\" \\\n  -H \"Authorization: Bearer <CF_TOKEN>\"\n```\n\nThe response should include `\"success\": true`\n\nand `\"status\": \"active\"`\n\n. In `.dev.vars`\n\nor deploy settings, set `CF_TOKEN`\n\nto the token secret value only. Do not include the word `Bearer`\n\n, do not use the token ID, and do not put a Global API Key in `CF_TOKEN`\n\n. For a Global API Key, set both `CF_EMAIL`\n\nand `CF_API_KEY`\n\ninstead.\n\nAlso check whether the token has an expiration, a `not_before`\n\ntime, or client IP restrictions. If you changed deploy variables in Cloudflare, redeploy so the Worker receives the new values.\n\n```\nnpm run deploy\n```\n\n`npm run deploy`\n\napplies remote D1 migrations before deploying. Cloudflare's deploy button can auto-provision the D1 database, R2 bucket, and queues declared in `wrangler.jsonc`\n\n; for manual deployments, create or update those bindings in Cloudflare if they do not already exist.", "url": "https://wpnews.pro/news/show-hn-mailflare-self-hosted-email-custom-domain-with-cloudflare", "canonical_source": "https://github.com/hieunc229/mailflare", "published_at": "2026-05-27 08:49:13+00:00", "updated_at": "2026-05-27 09:17:28.998180+00:00", "lang": "en", "topics": ["ai-products", "ai-tools", "ai-agents", "ai-startups", "natural-language-processing"], "entities": ["Mailflare", "Cloudflare"], "alternates": {"html": "https://wpnews.pro/news/show-hn-mailflare-self-hosted-email-custom-domain-with-cloudflare", "markdown": "https://wpnews.pro/news/show-hn-mailflare-self-hosted-email-custom-domain-with-cloudflare.md", "text": "https://wpnews.pro/news/show-hn-mailflare-self-hosted-email-custom-domain-with-cloudflare.txt", "jsonld": "https://wpnews.pro/news/show-hn-mailflare-self-hosted-email-custom-domain-with-cloudflare.jsonld"}}