Cloudsteading
Product image still needed. This listing has source documentation, but no reviewed screenshot yet.

EmailFlare

Run transactional email APIs and a team inbox on Cloudflare Workers.

EmailFlare is a self-hosted Postmark/Resend alternative built on Cloudflare (D1, Durable Objects, Email Workers, KV, Queues). Paid services required. Inspect the source and license in the linked repository.

Source & license

Upstream license: MIT

License TL;DR

You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.

Explain MIT in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@0xdps

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Paid services required

EmailFlare has a documented low-volume paid Cloudflare path beginning with Workers Paid ($5 USD/account/month), with separately metered usage. Arbitrary-recipient email requires Paid; database, storage and email usage above included allowances add charges.

Hosting requirements
  • Use Workers Paid for arbitrary-recipient email, starting at $5 USD/account/month; 3,000 outbound emails/month are included, then $0.35 per 1,000.
  • Workers Paid starts at $5 USD/account/month and includes 10 million requests/month plus 30 million CPU milliseconds/month; higher CPU limits do not make execution unmetered.
  • Workers Paid D1 includes 25 billion rows read/month, 50 million written/month and 5 GB storage; database usage above those amounts is metered.
  • Workers Paid KV includes 10 million reads/month, 1 million writes, deletes and lists/month each, plus 1 GB; additional usage is metered.
  • Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account.
  • Paid SQLite Durable Objects include 1 million requests/month, 400,000 GB-s duration/month and 5 GB-month SQL storage; active duration and excess database operations are metered.
  • Workers Paid Queues includes 1 million operations/month; count writes, reads, deletes, retries and each 64 KB chunk, with usage above the allowance metered.
  • Workers Paid Email Service includes 3,000 outbound emails/account/month, then $0.35 per 1,000; inbound Email Routing remains unmetered apart from Worker processing.
  • Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: d448ea2. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • resend ↗

    Both services are built on Cloudflare — Email Sending, Email Routing, D1, KV, R2, Queues, and Durable Objects — and deploy as **Cloudflare Workers** (primary, zero-ops) or as a single **Docker** container with embedded SQLite (secondary, for self-hosting).

  • postmark ↗

    Both services are built on Cloudflare — Email Sending, Email Routing, D1, KV, R2, Queues, and Durable Objects — and deploy as **Cloudflare Workers** (primary, zero-ops) or as a single **Docker** container with embedded SQLite (secondary, for self-hosting).

  • workers ↗

    { // ── Cloudflare Workers config template for emailflare ──────────────────────── // // ⚠ This is a TEMPLATE. It is tracked in git and contains REPLACE_WITH_* // placeholders. Do NOT put real resource IDs here. // // The setup script (scripts/setup.mjs / `just emailflare-api-worker-setup`) // generates the gitignored `wrangler.jsonc` from this template with your real IDs. // // ★ FIRST-TIME SETUP — one command does everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailf

  • d1 ↗

    everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update // // ★ GRADUAL ROLLOUT — upload a version and control traffic split: // pnpm rollout:upload # apply migrations + upload new version // wrangler versions deploy \ // --version-percentage <VERSION_ID>=10 # 10 % traffic // wrangler versions deploy \ // --version-percentage <VERSION_ID>=100 # full cutover // // Manual setup (if needed): // 1. `wrangler d1 create emailflare` → fill in database_id bel

  • kv ↗

    thing: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update // // ★ GRADUAL ROLLOUT — upload a version and control traffic split: // pnpm rollout:upload # apply migrations + upload new version // wrangler versions deploy \ // --version-percentage <VERSION_ID>=10 # 10 % traffic // wrangler versions deploy \ // --version-percentage <VERSION_ID>=100 # full cutover // // Manual setup (if needed): // 1. `wrangler d1 create emailflare` → fill in database_id below /

  • r2 ↗

    SETUP: // just emailflare-inbox-deploy // (creates D1, KV, R2, Queue, generates wrangler.jsonc, applies migrations, // sets secrets, builds inbox-ui, deploys) // // Manual setup (if needed): // 1. Create D1: wrangler d1 create emailflare-inbox → fill in database_id // 2. Create KV: wrangler kv namespace create emailflare-inbox-rate-limit → fill in id // 3. Create R2: wrangler r2 bucket create emailflare-inbox-attachments // 4. Apply migrations: pnpm migrations:apply:local // 5. Set secrets: // wrangler secret put SESSION_SECRET (min 32 chars, random) // wrangler secret put CF_API_TOKEN (CF token: Email Send + Zone + DNS) // wrangler se

  • durable-objects ↗

    d — Cloudflare error 10099). "migrations": [ { "tag": "v1", "new_sqlite_classes": ["NotificationsHub"], }, ], // Queue for sequence email delivery "queues": { "producers": [ { "binding": "EMAIL_QUEUE", "queue": "emailflare-inbox-sequences", }, ], "consumers": [ { "queue": "emailflare-inbox-sequences", "max_batch_size": 10, "max_batch_timeout": 30, }, ], }, // No hardcoded env vars here — all account/deployment-specific values are set // via `wrangler secret put` (see scripts/deploy-inbox.mjs + scripts/config.toml). // For local `wrangler dev`, copy .dev.vars.example → .dev.vars and fill in. // Workers Rate Limiting for API key calls (100 req / 60s pe

  • queues ↗

    "NotificationsHub"], }, ], // Queue for sequence email delivery "queues": { "producers": [ { "binding": "EMAIL_QUEUE", "queue": "emailflare-inbox-sequences", }, ], "consumers": [ { "queue": "emailflare-inbox-sequences", "max_batch_size": 10, "max_batch_timeout": 30, }, ], }, // No hardcoded env vars here — all account/deployment-specific values are set // via `wrangler secret put` (see scripts/deploy-inbox.mjs + scripts/config.toml). // For local `wrangler dev`, copy .dev.vars.example → .dev.vars and fill in. // Workers Rate Limiting for API key calls (100 req / 60s per key) "unsafe": { "bindings": [ { "name": "RATE_LIMITER", "type": "ratelimit"

  • email-workers ↗

    { // ── Cloudflare Workers config template for emailflare ──────────────────────── // // ⚠ This is a TEMPLATE. It is tracked in git and contains REPLACE_WITH_* // placeholders. Do NOT put real resource IDs here. // // The setup script (scripts/setup.mjs / `just emailflare-api-worker-setup`) // generates the gitignored `wrangler.jsonc` from this template with your real IDs. // // ★ FIRST-TIME SETUP — one command does everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update //

  • paid ↗

    { // ── Cloudflare Workers config template for emailflare ──────────────────────── // // ⚠ This is a TEMPLATE. It is tracked in git and contains REPLACE_WITH_* // placeholders. Do NOT put real resource IDs here. // // The setup script (scripts/setup.mjs / `just emailflare-api-worker-setup`) // generates the gitignored `wrangler.jsonc` from this template with your real IDs. // // ★ FIRST-TIME SETUP — one command does everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailf

  • paid ↗

    everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update // // ★ GRADUAL ROLLOUT — upload a version and control traffic split: // pnpm rollout:upload # apply migrations + upload new version // wrangler versions deploy \ // --version-percentage <VERSION_ID>=10 # 10 % traffic // wrangler versions deploy \ // --version-percentage <VERSION_ID>=100 # full cutover // // Manual setup (if needed): // 1. `wrangler d1 create emailflare` → fill in database_id bel

  • paid ↗

    thing: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update // // ★ GRADUAL ROLLOUT — upload a version and control traffic split: // pnpm rollout:upload # apply migrations + upload new version // wrangler versions deploy \ // --version-percentage <VERSION_ID>=10 # 10 % traffic // wrangler versions deploy \ // --version-percentage <VERSION_ID>=100 # full cutover // // Manual setup (if needed): // 1. `wrangler d1 create emailflare` → fill in database_id below /

  • paid ↗

    up>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 seconds)<br> Max of 15 minutes of CPU time per [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) or [Queue Consumer](https://developers.cloudflare.co

  • paid ↗

    oudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/observability/metrics-analytics/#query-via-the-graphql-api), or the [Cloudflare dashboard ↗︎](https://dash.cloudflare.com/?to=/:account/workers/d1/). Select your D1 database, then vie

  • paid ↗

    cing/). | | Free plan<sup>1</sup> | Paid plan | | --- | --- | --- | | Keys read | 100,000 / day | 10 million/month, + $0.50/million | | Keys written | 1,000 / day | 1 million/month, + $5.00/million | | Keys deleted | 1,000 / day | 1 million/month, + $5.00/million | | List requests | 1,000 / day | 1 million/month, + $5.00/million | | Stored data | 1 GB | 1 GB, + $0.50/ GB-month | <sup>1</sup> The Workers Free plan includes limited Workers KV usage. All limits reset daily at 00:00 UTC. If you exceed any one of these limits, further operations of that type will fail with an error. Note Workers KV pricing for read, write and delete operations is on a per-key basis. Bulk read operations are billed by the amount

  • paid ↗

    infrequent access storage) for 1.1 GB, you will be billed for 2 GB. ### Free tier You can use the following amount of storage and operations each month for free. | | Free | | --- | --- | | Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month | | Egress (data transfer to Internet) | Free <sup>[1](#user-content-fn-1)</sup> | Caution The free tier only applies to Standard storage, and does not apply to Infrequent Access storage. ### Storage usage Storage is billed using gigabyte-month (GB-month) as the billing metric. A GB-month is calculated by averaging the *peak* storage per day over a billing period (30 days). For examp

  • paid ↗

    jects are available both on Workers Free and Workers Paid plans. - **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available. - **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only available to accounts that already have a key-value-backed namespace. If you wish to downgrade from a Workers Paid plan to a Workers Free plan, you must first ensure that you have deleted all Durable Object namespaces with the key-value storage backend. On Workers Free plan: - If you exceed any one of the free tier limits, further operations of that type will fail with an error. - Daily free limits reset at 00:00 UTC. ## Compute billing Durable Objects are billed for compute duration (wall-clock time) while the Durable Object is actively running or is idle in memory but unable to [hibernate](https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/). Durable Objects that are idle and eligible for hibernation are not billed for duration, even before the runtime has hibernated them. Requests to a D

  • paid ↗

    dth) charges. | | Workers Free | Workers Paid | | --- | --- | --- | | Standard operations | 10,000 operations/day included | 1,000,000 operations/month included + $0.40/million operations | | Message retention | 24 hours (non-configurable) | 4 days default, configurable up to 14 days | In most cases, it takes 3 operations to deliver a message: 1 write, 1 read, and 1 delete. Therefore, you can use the following formula to estimate your monthly bill: ```txt ((Number of Messages * 3) - 1,000,000) / 1,000,000 * $0.40 ``` Additionally: - Each retry incurs a read operation. A batch of 10 messages that is retried would incur 10 operations for each retry. - Messages that reach the maximum retries and that are wr

  • paid ↗

    g is based on your Cloudflare plan and email usage. ## Plan pricing Email Routing is available on both the Workers Free and Workers Paid plans. Sending to arbitrary recipients requires the Workers Paid plan. Sending to [verified destination addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#destination-addresses) in your account is free on all plans, including when only Email Routing is configured. | | Workers Free | Workers Paid | | --- | --- | --- | | **Outbound emails (Email Sending)** | Not available | 3,000 included per month, then $0.35 per 1,000 emails | | **Inbound emails (Email Routing)** | Unlimited | Unlimited | The 3,000 included emails apply per a

  • paid ↗

    billed accordingly. | | Free plan | Paid plan | | --- | --- | --- | | Requests | 100,000 / day | 1 million / month, + $0.15/million<br> Includes HTTP requests, RPC sessions<sup>1</sup>, WebSocket messages<sup>2</sup>, and alarm invocations | | Duration<sup>3</sup> | 13,000 GB-s / day | 400,000 GB-s / month, + $12.50/million GB-s<sup>4,5</sup> | <details> <summary> Footnotes </summary> <sup>1</sup> Each <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/">RPC session</a> is billed as one request to your Durable Object. Every <a href="https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/">RPC method call</a> on a <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects stub</a> is its own RPC session and therefore a single billed request. RPC method calls can return objects (stubs) extending <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/#lifetimes-memory-and-resource-management"><code>RpcTarget</code></a> and invo

  • paid ↗

    /). | | Workers Free plan | Workers Paid plan | | --- | --- | --- | | Rows reads <sup>1,2</sup> | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written <sup>1,2,3,4</sup> | 100,000 / day | First 50 million / month included + $1.00 / million rows | | SQL Stored data <sup>5</sup> | 5 GB (total) | 5 GB-month, + $0.20/ GB-month | <details> <summary> Footnotes </summary> <sup>1</sup> Rows read and rows written included limits and rates match <a href="https://developers.cloudflare.com/d1/platform/pricing/">D1 pricing</a>, Cloudflare's serverless SQL database. <sup>2</sup> Key-value methods like <code>get()</code>, <code>put()</code>, <code>delete()</code>, or <code>list(

  • paid ↗

    es Functions, Workers KV, Hyperdrive, and Durable Objects usage for a minimum charge of $5 USD per month for an account. The plan includes increased initial usage allotments, with clear charges for usage that exceeds the base plan. There are no additional charges for data transfer (egress) or throughput (bandwidth). All included usage is on a monthly basis. Pages Functions billing All [Pages Functions](https://developers.cloudflare.com/pages/functions/) are billed as Workers. All pricing and inclusions in this document apply to Pages Functions. Refer to [Functions Pricing](https://developers.cloudflare.com/pages/functions/pricing/) for more information on Pages Functions pricing. ## Workers Users on the Wo

  • paid ↗

    Routing is available on both the Workers Free and Workers Paid plans. Sending to arbitrary recipients requires the Workers Paid plan. Sending to [verified destination addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#destination-addresses) in your account is free on all plans, including when only Email Routing is configured. | | Workers Free | Workers Paid | | --- | --- | --- | | **Outbound emails (Email Sending)** | Not available | 3,000 included per month, then $0.35 per 1,000 emails | | **Inbound emails (Email Routing)** | Unlimited | Unlimited | The 3,000 included emails apply per account, per month, aligned with your Cloudflare subscription billing cycle. Emails that hard-bounce or are otherwise accepted by Email Service count toward the quota. Emails rejected at the API boundary, including sends blocke

  • MIT ↗

    MIT License Copyright (c) 2026 0xdps Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMEN

  • architecture ↗

    { // ── Cloudflare Workers config template for emailflare ──────────────────────── // // ⚠ This is a TEMPLATE. It is tracked in git and contains REPLACE_WITH_* // placeholders. Do NOT put real resource IDs here. // // The setup script (scripts/setup.mjs / `just emailflare-api-worker-setup`) // generates the gitignored `wrangler.jsonc` from this template with your real IDs. // // ★ FIRST-TIME SETUP — one command does everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailf

  • architecture ↗

    everything: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update // // ★ GRADUAL ROLLOUT — upload a version and control traffic split: // pnpm rollout:upload # apply migrations + upload new version // wrangler versions deploy \ // --version-percentage <VERSION_ID>=10 # 10 % traffic // wrangler versions deploy \ // --version-percentage <VERSION_ID>=100 # full cutover // // Manual setup (if needed): // 1. `wrangler d1 create emailflare` → fill in database_id bel

  • architecture ↗

    thing: // just emailflare-api-worker-setup // (creates D1 + KV, generates wrangler.jsonc, applies migrations, sets secrets, deploys) // // ★ DEPLOY UPDATES — migrates schema then redeploys atomically: // just emailflare-api-worker-update // // ★ GRADUAL ROLLOUT — upload a version and control traffic split: // pnpm rollout:upload # apply migrations + upload new version // wrangler versions deploy \ // --version-percentage <VERSION_ID>=10 # 10 % traffic // wrangler versions deploy \ // --version-percentage <VERSION_ID>=100 # full cutover // // Manual setup (if needed): // 1. `wrangler d1 create emailflare` → fill in database_id below /

  • architecture ↗

    SETUP: // just emailflare-inbox-deploy // (creates D1, KV, R2, Queue, generates wrangler.jsonc, applies migrations, // sets secrets, builds inbox-ui, deploys) // // Manual setup (if needed): // 1. Create D1: wrangler d1 create emailflare-inbox → fill in database_id // 2. Create KV: wrangler kv namespace create emailflare-inbox-rate-limit → fill in id // 3. Create R2: wrangler r2 bucket create emailflare-inbox-attachments // 4. Apply migrations: pnpm migrations:apply:local // 5. Set secrets: // wrangler secret put SESSION_SECRET (min 32 chars, random) // wrangler secret put CF_API_TOKEN (CF token: Email Send + Zone + DNS) // wrangler se

  • architecture ↗

    d — Cloudflare error 10099). "migrations": [ { "tag": "v1", "new_sqlite_classes": ["NotificationsHub"], }, ], // Queue for sequence email delivery "queues": { "producers": [ { "binding": "EMAIL_QUEUE", "queue": "emailflare-inbox-sequences", }, ], "consumers": [ { "queue": "emailflare-inbox-sequences", "max_batch_size": 10, "max_batch_timeout": 30, }, ], }, // No hardcoded env vars here — all account/deployment-specific values are set // via `wrangler secret put` (see scripts/deploy-inbox.mjs + scripts/config.toml). // For local `wrangler dev`, copy .dev.vars.example → .dev.vars and fill in. // Workers Rate Limiting for API key calls (100 req / 60s pe

  • architecture ↗

    "NotificationsHub"], }, ], // Queue for sequence email delivery "queues": { "producers": [ { "binding": "EMAIL_QUEUE", "queue": "emailflare-inbox-sequences", }, ], "consumers": [ { "queue": "emailflare-inbox-sequences", "max_batch_size": 10, "max_batch_timeout": 30, }, ], }, // No hardcoded env vars here — all account/deployment-specific values are set // via `wrangler secret put` (see scripts/deploy-inbox.mjs + scripts/config.toml). // For local `wrangler dev`, copy .dev.vars.example → .dev.vars and fill in. // Workers Rate Limiting for API key calls (100 req / 60s per key) "unsafe": { "bindings": [ { "name": "RATE_LIMITER", "type": "ratelimit"

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

Resend logoResend ↗

Transactional email API sending, templates, delivery records and suppression management; full commercial deliverability operations, SLAs and every provider integration are excluded.

See supporting source ↗
Postmark logoPostmark ↗

Transactional email API sending, templates, delivery records and suppression management; full commercial deliverability operations, SLAs and every provider integration are excluded.

See supporting source ↗
external SaaS target
varies
→ D1 + Durable Objects + Email Workers
external SaaS target
varies
→ D1 + Durable Objects + Email Workers

How it works

The shape of EmailFlare on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points3
emailflare-api-bridge
services/email-bridge/wrangler.jsonc
emailflare-inbox-bridge
services/inbox-bridge/wrangler.jsonc
emailflare
services/landing/wrangler.jsonc
↓
App
emailflare-api-bridge
entry
Cloudflare Workers
Entrypoint: src/index.ts
emailflare-inbox-bridge
entry
Cloudflare Workers
Entrypoint: src/index.ts
emailflare
entry
Cloudflare Workers
↓

Configuration and workflow sources

Reviewed commit d448ea205937. Files were read as data; upstream applications and CI jobs were not executed.

Partial source coverage: 46 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.

Deployment configuration · 5 files
services/email-bridge/wrangler.jsonc ↗

Cloudflare Workers · compatibility 2025-05-01

emailflare-api-bridge · default

Entrypoint: src/index.ts

    No resource bindings declared in this scope.

    services/inbox-bridge/wrangler.jsonc ↗

    Cloudflare Workers · compatibility 2025-01-01

    emailflare-inbox-bridge · default

    Entrypoint: src/index.ts

      No resource bindings declared in this scope.

      services/email-worker/wrangler.example.jsonc ↗

      Cloudflare Workers · example/template, excluded from overview · compatibility 2025-05-01

      emailflare-api-worker · default

      Entrypoint: src/index.ts

      Static assets: ../email-ui/dist · single-page-application

      • DB → D1
      • RATE_LIMIT_KV → KV
      • ASSETS → Static assets
      services/inbox-worker/wrangler.example.jsonc ↗

      Cloudflare Workers · example/template, excluded from overview · compatibility 2025-05-01

      emailflare-inbox-worker · default

      Entrypoint: src/index.ts

      Static assets: ../inbox-ui/dist · single-page-application

      Cron triggers (UTC): */5 * * * *

      • DB → D1
      • RATE_LIMIT_KV → KV
      • ATTACHMENTS → R2
      • NOTIFICATIONS → Durable Objects · class NotificationsHub
      • EMAIL_QUEUE → Queues (producer) · queue emailflare-inbox-sequences
      • emailflare-inbox-sequences → Queues (consumer) · queue emailflare-inbox-sequences
      • ASSETS → Static assets
      services/landing/wrangler.jsonc ↗

      Cloudflare Workers · compatibility 2026-04-28

      emailflare · default

      Static assets: ./dist · 404-page

      • Static assets → Static assets

      Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

      Runtime source · handlers, binding usage and workflow steps

      Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

      scripts/setup.mjs ↗
      • L37 · loadConfig calls (conditional paths may differ): existsSync, parseToml, readFileSync, die
      • L69 · run calls (conditional paths may differ): execSync
      • L77 · capture calls (conditional paths may differ): spawnSync
      • L87 · log calls (conditional paths may differ): process.stdout.write
      • L91 · ok calls (conditional paths may differ): process.stdout.write
      • L95 · warn calls (conditional paths may differ): process.stdout.write
      • L99 · die calls (conditional paths may differ): process.stderr.write, process.exit
      • L104 · promptSecret calls (conditional paths may differ): process.stdout.write, createInterface, process.stdin.setRawMode, chunk.toString, process.stdin.removeListener, rl.close, resolve, process.exit, value.slice, process.stdin.on, process.stdin.resume
      • L115 · onData calls (conditional paths may differ): chunk.toString, process.stdin.setRawMode, process.stdin.removeListener, rl.close, process.stdout.write, resolve, process.exit, value.slice

      Environment references: process.env.EMAILFLARE_DB_NAME · process.env.CLOUDFLARE_API_TOKEN · process.env.CLOUDFLARE_ACCOUNT_ID

      services/email-bridge/src/index.ts ↗
      • L17 · email handler exported · references EMAIL_SERVER_URL, WEBHOOK_SECRET · calls arrayBuffer, env.EMAIL_SERVER_URL.replace, fetch

      Environment references: env.EMAIL_SERVER_URL · env.WEBHOOK_SECRET

      services/inbox-bridge/src/index.ts ↗
      • L31 · email handler exported · references INBOX_WEBHOOK_SECRET, INBOX_SERVER_URL · calls arrayBuffer, btoa, String.fromCharCode, JSON.stringify, message.headers.get, hmacSha256Hex, env.INBOX_SERVER_URL.replace, fetch
      • L16 · hmacSha256Hex calls (conditional paths may differ): crypto.subtle.importKey, encode, crypto.subtle.sign, join, map, Array.from, padStart, b.toString

      Environment references: env.INBOX_WEBHOOK_SECRET · env.INBOX_SERVER_URL

      services/email-worker/src/index.ts ↗
      • L144 · fetch handler exported
      • L150 · email handler exported · calls handleInboundEmail
      • L57 · app.use("*")
      • L60 · app.use("/v1/*")
      • L69 · app.use("/api/*")
      • L84 · app.get("/health")
      • L87 · app.route("/v1/unsubscribe")
      • L90 · app.use("/v1/send")
      • L91 · app.use("/v1/send")
      • L92 · app.route("/v1/send")
      • L95 · app.route("/api/auth")
      • L99 · admin.use("*")
      • L102 · admin.get("/layouts")
      • L107 · admin.post("/layouts/:id/preview")
      • L115 · admin.route("/domains")
      • L116 · admin.route("/templates")
      • L117 · admin.route("/keys")
      • L118 · admin.route("/logs")
      • L119 · admin.route("/stats")
      • L120 · admin.route("/cloudflare")
      • L121 · admin.route("/suppressions")
      • L122 · admin.route("/lists")
      • L124 · app.route("/api")
      • L137 · app.post("/api/_seed")
      • L42 · parseAdminOrigins calls (conditional paths may differ): map, filter, raw.split, d.trim, d.startsWith

      Environment references: c.env.ADMIN_ORIGIN · c.env.DB

      services/inbox-worker/src/index.ts ↗
      • L161 · fetch handler exported · calls app.fetch
      • L166 · email handler exported · calls handleIncomingEmail
      • L171 · scheduled handler exported · calls processDueSequenceSteps
      • L176 · queue handler exported · calls handleSequenceQueueMessage, msg.ack, msg.retry
      • L68 · app.use("/v1/*")
      • L78 · app.get("/health")
      • L79 · app.route("/api/setup")
      • L80 · app.route("/api/auth")
      • L81 · app.route("/api")
      • L84 · app.route("/v1/unsubscribe")
      • L87 · app.use("/v1/*")
      • L88 · app.route("/v1/send")
      • L92 · adminApp.use("/*")
      • L94 · adminApp.route("/domains")
      • L95 · adminApp.route("/keys")
      • L96 · adminApp.route("/logs")
      • L97 · adminApp.route("/templates")
      • L98 · adminApp.route("/stats")
      • L99 · adminApp.route("/cloudflare")
      • L100 · adminApp.route("/lists")
      • L101 · adminApp.route("/suppressions")
      • L105 · testMailboxApp.use("/*")
      • L106 · testMailboxApp.route("/")
      • L107 · adminApp.route("/test-emails")
      • L110 · adminApp.route("/inbox/people")
      • L111 · adminApp.route("/inbox/compose")
      • L112 · adminApp.route("/inbox/inboxes")
      • L113 · adminApp.route("/inbox/sequences")
      • L114 · adminApp.route("/inbox/templates")
      • L118 · adminOnlyApp.use("/*")
      • L119 · adminOnlyApp.route("/users")
      • L120 · adminApp.route("/admin")
      • L123 · adminApp.get("/notifications/ws")
      • L131 · adminApp.post("/seed")
      • L136 · app.route("/api")
      • L148 · app.all("*")

      Environment references: c.env.NOTIFICATIONS · c.env.DB · c.env.ASSETS

      services/email-worker/src/middleware/auth.ts ↗
      • L19 · secretKey calls (conditional paths may differ): encode
      • L23 · getSession calls (conditional paths may differ): getCookie, jwtVerify, secretKey, Boolean
      • L34 · saveSession calls (conditional paths may differ): sign, setExpirationTime, setIssuedAt, setProtectedHeader, secretKey, setCookie
      • L52 · clearSession calls (conditional paths may differ): deleteCookie

      Environment references: c.env.SESSION_SECRET

      services/email-worker/src/middleware/apiKey.ts ↗
      • L9 · sha256Hex calls (conditional paths may differ): encode, crypto.subtle.digest, join, map, Array.from, padStart, b.toString

      Environment references: c.env.DB

      services/email-worker/src/middleware/rateLimit.ts ↗

        Environment references: c.env.RATE_LIMITER

        services/email-worker/src/seed.ts ↗
        • L62 · seedSystemTemplates calls (conditional paths may differ): makeDb, toISOString, Object.entries, templates.findOne, templates.insert, generateId
        services/email-worker/src/routes/auth.ts ↗
        • L27 · app.post("/login")
        • L44 · app.post("/logout")
        • L50 · app.get("/me")
        • L11 · timingSafeEqual calls (conditional paths may differ): crypto.subtle.generateKey, Promise.all, crypto.subtle.sign, encoder.encode

        Environment references: c.env.RATE_LIMIT_KV · c.env.ADMIN_TOKEN

        services/email-worker/src/routes/domains.ts ↗
        • L18 · app.get("/")
        • L25 · app.get("/:id")
        • L33 · app.post("/")
        • L91 · app.get("/:id/dns")
        • L102 · app.post("/:id/verify")
        • L123 · app.delete("/:id")

        Environment references: c.env.DB · c.env.CF_API_TOKEN

        services/email-worker/src/routes/templates.ts ↗
        • L13 · app.get("/")
        • L27 · app.get("/themes")
        • L37 · app.get("/:idOrSlug")
        • L47 · app.post("/")
        • L73 · app.put("/:id")
        • L110 · app.delete("/:id")
        • L121 · app.post("/:id/preview")

        Environment references: c.env.DB

        services/email-worker/src/routes/keys.ts ↗
        • L18 · app.get("/")
        • L25 · app.post("/")
        • L59 · app.delete("/:id")
        • L8 · randomHex calls (conditional paths may differ): crypto.getRandomValues, join, map, Array.from, padStart, b.toString

        Environment references: c.env.DB

        services/email-worker/src/routes/logs.ts ↗
        • L9 · app.get("/")
        • L76 · app.get("/:id")

        Environment references: c.env.DB

        services/email-worker/src/routes/stats.ts ↗
        • L27 · app.get("/")
        • L9 · rangeStart calls (conditional paths may differ): now.setDate, now.getDate, now.setHours, now.toISOString

        Environment references: c.env.DB

        services/email-worker/src/routes/cloudflare.ts ↗
        • L10 · app.get("/status")
        • L19 · app.post("/backfill-domains")

        Environment references: c.env.CF_API_TOKEN · c.env.CF_ACCOUNT_ID · c.env.DB

        services/email-worker/src/routes/send.ts ↗
        • L20 · app.post("/")

        Environment references: c.env.DB · c.env.PUBLIC_URL · c.env.CF_API_TOKEN · c.env.CF_ACCOUNT_ID

        services/email-worker/src/routes/suppressions.ts ↗
        • L16 · app.get("/")
        • L63 · app.post("/")
        • L86 · app.delete("/:id")

        Environment references: c.env.DB

        services/email-worker/src/routes/lists.ts ↗
        • L15 · app.get("/")
        • L22 · app.post("/")
        • L39 · app.delete("/:id")

        Environment references: c.env.DB

        services/email-worker/src/routes/unsubscribe.ts ↗
        • L42 · app.get("/")
        • L55 · app.post("/")
        • L17 · resolveToken calls (conditional paths may differ): db.query
        • L25 · performUnsubscribe calls (conditional paths may differ): makeDb, resolveToken, db.run, generateId, rec.email.toLowerCase, toISOString

        Environment references: env.DB

        services/email-worker/src/email-handler.ts ↗
        • L31 · isBounce calls (conditional paths may differ): toLowerCase, BOUNCE_SUBJECTS.some, sub.includes, BOUNCE_FROM_PATTERNS.some, from.includes
        • L37 · isComplaint calls (conditional paths may differ): toLowerCase, email.headers.find, h.key.toLowerCase, includes, ct.toLowerCase, COMPLAINT_SUBJECTS.some, sub.includes, COMPLAINT_FROM_PATTERNS.some, from.includes
        • L51 · extractRecipient calls (conditional paths may differ): content.match, toLowerCase, trim
        • L72 · classifyBounce calls (conditional paths may differ): content.match, code.startsWith, test, spamPhrases.some, includes, content.toLowerCase, hardPhrases.some
        • L120 · extractReason calls (conditional paths may differ): content.match, slice, split, trim
        • L139 · handleInboundEmail calls (conditional paths may differ): arrayBuffer, log.warn, parse, join, toISOString, isBounce, processBounce, isComplaint, processComplaint
        • L174 · processBounce calls (conditional paths may differ): extractRecipient, log.warn, classifyBounce, extractReason, toISOString, Date.now, db.query, db.run, generateId, log.info, Number
        • L247 · processComplaint calls (conditional paths may differ): extractRecipient, log.warn, toISOString, Date.now, db.query, db.run, generateId, log.info

        Environment references: env.DB

        services/inbox-worker/src/routes/setup.ts ↗
        • L15 · app.get("/status")
        • L27 · app.post("/")

        Environment references: c.env.DB

        Build and deployment pipeline · 3 GitHub Actions workflows

        Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

        Deploy Cloudflare Workers · .github/workflows/deploy-workers.yml ↗

        Triggers: workflow_dispatch

        Deploy emailflare-api-worker · no job dependencies declared

        Condition: ${{ github.event.inputs.target == 'cf-api' || github.event.inputs.target == 'all' }}

        1. actions/checkout@v4actions/checkout@v4
        2. Enable Corepackcorepack enable
        3. actions/setup-node@v4actions/setup-node@v4
        4. Install workspace dependenciespnpm install --frozen-lockfile
        5. Build shared packagespnpm run build:shared
        6. Build admin UI (email-ui)pnpm install --frozen-lockfile pnpm build
        7. Install worker depspnpm install --frozen-lockfile
        8. Audit dependenciespnpm audit --audit-level=high
        9. Generate wrangler.jsoncsed -e "s/REPLACE_WITH_D1_DATABASE_ID/${{ vars.EMAIL_WORKER_D1_ID }}/g" \ -e "s/REPLACE_WITH_KV_NAMESPACE_ID/${{ vars.EMAIL_WORKER_KV_ID }}/g" \ wrangler.example.jsonc > wrangler.jsonc
        10. Apply D1 migrationspnpm run migrations:apply
        11. Deploy workernpx wrangler deploy

        Deploy emailflare-inbox-worker · no job dependencies declared

        Condition: ${{ github.event.inputs.target == 'cf-inbox' || github.event.inputs.target == 'all' }}

        1. actions/checkout@v4actions/checkout@v4
        2. Enable Corepackcorepack enable
        3. actions/setup-node@v4actions/setup-node@v4
        4. Install workspace dependenciespnpm install --frozen-lockfile
        5. Build shared packagespnpm run build:shared
        6. Build inbox UI (inbox-ui)pnpm install --frozen-lockfile pnpm build
        7. Install worker depspnpm install --frozen-lockfile
        8. Audit dependenciespnpm audit --audit-level=high
        9. Generate wrangler.jsoncsed -e "s/REPLACE_WITH_D1_DATABASE_ID/${{ vars.EMAIL_WORKER_D1_ID }}/g" \ -e "s/REPLACE_WITH_KV_NAMESPACE_ID/${{ vars.INBOX_WORKER_KV_ID }}/g" \ wrangler.example.jsonc > wrangler.jsonc
        10. Apply D1 migrationspnpm run migrations:apply
        11. Deploy workernpx wrangler deploy

        Deploy bridges · no job dependencies declared

        Condition: ${{ github.event.inputs.target == 'cf-worker' || github.event.inputs.target == 'all' }}

        1. actions/checkout@v4actions/checkout@v4
        2. Enable Corepackcorepack enable
        3. actions/setup-node@v4actions/setup-node@v4
        4. Install workspace dependenciespnpm install --frozen-lockfile
        5. Deploy bridgenpx wrangler deploy
        Docker Build Check · .github/workflows/docker-check.yml ↗

        Triggers: push

        Check emailflare-api Dockerfile · no job dependencies declared

        1. actions/checkout@v4actions/checkout@v4
        2. docker/setup-buildx-action@v3docker/setup-buildx-action@v3
        3. Build (no push)docker/build-push-action@v6

        Check emailflare-inbox Dockerfile · no job dependencies declared

        1. actions/checkout@v4actions/checkout@v4
        2. docker/setup-buildx-action@v3docker/setup-buildx-action@v3
        3. Build (no push)docker/build-push-action@v6

        Verify ${{ matrix.name }} · no job dependencies declared

        1. actions/checkout@v4actions/checkout@v4
        2. Enable Corepackcorepack enable
        3. actions/setup-node@v4actions/setup-node@v4
        4. Install workspace dependenciespnpm install --frozen-lockfile
        5. Build shared packagespnpm run build:shared
        6. Check (${{ matrix.check }})${{ matrix.check }}
        7. Audit dependenciespnpm audit --audit-level=high
        Build, Verify & Publish · .github/workflows/docker-publish.yml ↗

        Triggers: push, workflow_dispatch

        Verify API · ${{ matrix.name }} · no job dependencies declared

        1. actions/checkout@v4actions/checkout@v4
        2. Enable Corepackcorepack enable
        3. actions/setup-node@v4actions/setup-node@v4
        4. Install workspace dependenciespnpm install --frozen-lockfile
        5. Build shared packagespnpm run build:shared
        6. Buildpnpm run build
        7. Audit dependenciespnpm audit --audit-level=high

        Verify Inbox · ${{ matrix.name }} · no job dependencies declared

        1. actions/checkout@v4actions/checkout@v4
        2. Enable Corepackcorepack enable
        3. actions/setup-node@v4actions/setup-node@v4
        4. Install workspace dependenciespnpm install --frozen-lockfile
        5. Build shared packagespnpm run build:shared
        6. Buildpnpm run build
        7. Audit dependenciespnpm audit --audit-level=high

        Build emailflare-api image · after verify-api

        1. actions/checkout@v4actions/checkout@v4
        2. Resolve image nameif [[ "${{ github.ref_name }}" =~ ^s[0-9] ]]; then echo "image=${{ env.REGISTRY }}/${{ github.repository_owner }}/emailflare-api-staging" >> $GITHUB_OUTPUT echo "is_staging=true" >> $GITHUB_OUTPUT else echo "image=${{ env.REGISTRY }}/${{ github.repository_owner }}/emailflare-api" >> $GITHUB_OUTPUT echo "is_staging=false" >> $GITHUB_OUTPUT fi
        3. docker/setup-qemu-action@v3docker/setup-qemu-action@v3
        4. docker/setup-buildx-action@v3docker/setup-buildx-action@v3
        5. Log in to GitHub Container Registrydocker/login-action@v3Condition: github.event_name != 'pull_request'
        6. Extract Docker metadatadocker/metadata-action@v5
        7. Build and pushdocker/build-push-action@v6

        Build emailflare-inbox image · after verify-inbox

        1. actions/checkout@v4actions/checkout@v4
        2. Resolve image nameif [[ "${{ github.ref_name }}" =~ ^s[0-9] ]]; then echo "image=${{ env.REGISTRY }}/${{ github.repository_owner }}/emailflare-inbox-staging" >> $GITHUB_OUTPUT echo "is_staging=true" >> $GITHUB_OUTPUT else echo "image=${{ env.REGISTRY }}/${{ github.repository_owner }}/emailflare-inbox" >> $GITHUB_OUTPUT echo "is_staging=false" >> $GITHUB_OUTPUT fi
        3. docker/setup-qemu-action@v3docker/setup-qemu-action@v3
        4. docker/setup-buildx-action@v3docker/setup-buildx-action@v3
        5. Log in to GitHub Container Registrydocker/login-action@v3Condition: github.event_name != 'pull_request'
        6. Extract Docker metadatadocker/metadata-action@v5
        7. Build and pushdocker/build-push-action@v6
        package.json ↗
        • build:email-core: pnpm --filter @emailflare/email-core build
        • build:inbox-core: pnpm --filter @emailflare/inbox-core build
        • build:emails: pnpm --filter @emailflare/emails build
        • build:shared: pnpm run build:email-core && pnpm run build:inbox-core && pnpm run build:emails
        • build:email-server: pnpm --filter @emailflare/email-server build
        • build:inbox-server: pnpm --filter @emailflare/inbox-server build
        • build:email-ui: pnpm --filter @emailflare/email-ui build
        • build:inbox-ui: pnpm --filter @emailflare/inbox-ui build
        services/email-ui/package.json ↗
        • build: vite build && tsc --noEmit
        services/email-worker/package.json ↗
        • deploy: wrangler deploy
        • migrations:apply:local: wrangler d1 migrations apply emailflare --local
        • migrations:apply: wrangler d1 migrations apply emailflare --remote
        services/inbox-server/package.json ↗
        • build: tsc
        • migrate: node dist/migrate.js
        services/inbox-ui/package.json ↗
        • build: vite build && tsc --noEmit
        services/inbox-worker/package.json ↗
        • deploy: wrangler deploy
        • migrations:apply:local: wrangler d1 migrations apply emailflare --local
        • migrations:apply: wrangler d1 migrations apply emailflare --remote
        • build:packages: pnpm --filter @emailflare/email-core build && pnpm --filter @emailflare/inbox-core build
        • build:ui: pnpm --filter @emailflare/inbox-ui build
        • deploy:full: pnpm run build:packages && pnpm run build:ui && wrangler d1 migrations apply emailflare --remote && wrangler deploy
        services/landing/package.json ↗
        • build: astro build
        • deploy: pnpm build && wrangler deploy

        Full upstream document by @0xdps · README.md · snapshot d448ea2

        EmailFlare logo

        EmailFlare

        EmailFlare is a Cloudflare-native email platform with two independent services that work together:

        • emailflare-api — transactional email sending API with an admin dashboard (domains, templates, API keys, logs, suppressions, lists + one-click unsubscribe)
        • emailflare-inbox — team inbox and lightweight CRM (receive, thread, reply, sequences, multi-user, lists + one-click unsubscribe)

        Both services are built on Cloudflare — Email Sending, Email Routing, D1, KV, R2, Queues, and Durable Objects — and deploy as Cloudflare Workers (primary, zero-ops) or as a single Docker container with embedded SQLite (secondary, for self-hosting).


        emailflare-api · Email Sending API

        A Hono API for sending transactional email via the Cloudflare Email Sending API, with a React admin panel for managing domains, templates, API keys, and logs.

        Cloudflare Worker deployment (primary — no Docker, edge-native)

        just install
        cp scripts/config.example.toml scripts/config.toml
        # fill in your values
        just emailflare-api-worker-setup
        

        Read the full guide: docs/CLOUDFLARE.md

        Docker self-host (secondary)

        gcr.io/0xdps/emailflare-api:latest
        
        cp .env.api.example .env.local
        # fill in SESSION_SECRET, ADMIN_TOKEN, CF_API_TOKEN, CF_ACCOUNT_ID
        
        docker compose --env-file .env.local -f deploy/docker/compose.email-api.yaml up -d
        

        Open http://localhost:8090. Read the full guide: docs/SELF_HOSTING.md

        Railway

        Deploy on Railway


        emailflare-inbox · Team Inbox & CRM

        A Node.js inbox server with a React dashboard for receiving inbound email via Cloudflare Email Routing, threading conversations, replying, running sequences, and managing contacts across a team.

        Cloudflare Worker deployment (primary — inbox-worker, D1 + R2 + KV + DO + Queues)

        just install
        cp scripts/config.example.toml scripts/config.toml
        # fill in your values
        just emailflare-inbox-deploy
        

        Read the full guide: docs/CLOUDFLARE.md

        Docker self-host (secondary)

        ghcr.io/0xdps/emailflare-inbox:latest
        
        cp .env.inbox.example .env.inbox.local
        # fill in SESSION_SECRET, WEBHOOK_SECRET, CF_API_TOKEN, CF_ACCOUNT_ID, REDIS_URL
        
        docker compose --env-file .env.inbox.local -f deploy/docker/compose.email-inbox.yaml up -d
        

        Open http://localhost:8091. Read the full guide: docs/SELF_HOSTING.md


        What ships in this repo

        Email API

        • services/email-worker — Cloudflare Worker bundling API + admin UI (D1 + KV) — primary deploy
        • services/email-server — Hono API (Node.js): domains, templates, keys, stats, send — for Docker
        • services/email-ui — React admin panel (Vite + TanStack Router)
        • services/email-bridge — CF Worker: receives bounce/complaint email and forwards to email-server webhook
        • deploy/docker/Dockerfile.email-api — production image for emailflare-api
        • deploy/docker/compose.email-api.yaml — single-container production compose
        • deploy/docker/compose.email-api.dev.yaml — local dev stack with hot reload

        Inbox

        • services/inbox-worker — Cloudflare Worker variant of the inbox (D1 + R2 + KV + DO + Queues) — primary deploy
        • services/inbox-server — Hono inbox API (Node.js): inboxes, people, threads, sequences, templates — for Docker
        • services/inbox-ui — React inbox dashboard (Vite + TanStack Router)
        • services/inbox-bridge — CF Worker: receives inbound email via CF Email Routing, forwards to inbox-server
        • deploy/docker/Dockerfile.email-inbox — production image for emailflare-inbox
        • deploy/docker/compose.email-inbox.yaml — single-container production compose
        • deploy/docker/compose.email-inbox.dev.yaml — local dev stack with hot reload

        Shared

        • services/emails — shared email layouts and rendering used by both servers
        • scripts/ — setup tooling for CF Worker deployments (setup.mjs, config.example.toml)
        • justfile — task runner for dev, prod, and Cloudflare Worker operations (docs/RECIPES.md)
        • docs/CLOUDFLARE.md — Cloudflare Workers deployment guide (primary)
        • docs/SELF_HOSTING.md — Docker self-hosting guide (secondary)

        Deployment modes

        EmailFlare supports two deployment modes for each product:

        Mode Products Stack When to use
        Cloudflare Workers (primary) emailflare-api, emailflare-inbox D1 + KV (+ R2, Queues, DO for inbox) Default — zero-ops, edge-native
        Docker (secondary) emailflare-api, emailflare-inbox single container + embedded SQLite Self-hosting on your own VPS/VM

        The Docker path uses SQLite via embedded mesahub-core (one image, one volume at /data).

        Read the full guides: docs/CLOUDFLARE.md · docs/SELF_HOSTING.md


        Minimum required environment

        emailflare-api (.env.local):

        Variable Description
        ADMIN_TOKEN Admin API token (32+ chars)
        SESSION_SECRET Session signing secret (32+ chars)
        MESAHUB_URL mh://local/emailflare for embedded SQLite
        CF_API_TOKEN Cloudflare token with Email Sending + Zone permissions
        CF_ACCOUNT_ID Cloudflare account ID
        PUBLIC_URL Public base URL, used for one-click unsubscribe links (optional)

        emailflare-inbox (.env.inbox.local):

        Variable Description
        SESSION_SECRET Session signing secret (32+ chars)
        WEBHOOK_SECRET Shared secret for inbox-bridge webhook auth
        MESAHUB_URL mh://local/inbox-db for embedded SQLite
        REDIS_URL Redis connection string (rate limiting + BullMQ)
        CF_API_TOKEN Cloudflare token for sending replies
        CF_ACCOUNT_ID Cloudflare account ID
        PUBLIC_URL Public base URL, used for one-click unsubscribe links in sequences (optional)

        Open source

        Frequently asked about EmailFlare

        What is EmailFlare?+

        EmailFlare is a self-hosted Postmark/Resend alternative built on the Cloudflare developer platform. Run transactional email APIs and a team inbox on Cloudflare Workers.

        What does EmailFlare replace?+

        EmailFlare is listed as an alternative to Postmark, Resend. Compare the features and tradeoffs before migrating.

        What Cloudflare primitives does EmailFlare use?+

        EmailFlare is built on D1, Durable Objects, Email Workers, KV, Queues, R2, Workers.

        How much does EmailFlare cost to run?+

        EmailFlare has a documented low-volume paid Cloudflare path beginning with Workers Paid ($5 USD/account/month), with separately metered usage. Arbitrary-recipient email requires Paid; database, storage and email usage above included allowances add charges. Use Workers Paid for arbitrary-recipient email, starting at $5 USD/account/month; 3,000 outbound emails/month are included, then $0.35 per 1,000. Workers Paid starts at $5 USD/account/month and includes 10 million requests/month plus 30 million CPU milliseconds/month; higher CPU limits do not make execution unmetered. Workers Paid D1 includes 25 billion rows read/month, 50 million written/month and 5 GB storage; database usage above those amounts is metered. Workers Paid KV includes 10 million reads/month, 1 million writes, deletes and lists/month each, plus 1 GB; additional usage is metered. Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account. Paid SQLite Durable Objects include 1 million requests/month, 400,000 GB-s duration/month and 5 GB-month SQL storage; active duration and excess database operations are metered. Workers Paid Queues includes 1 million operations/month; count writes, reads, deletes, retries and each 64 KB chunk, with usage above the allowance metered. Workers Paid Email Service includes 3,000 outbound emails/account/month, then $0.35 per 1,000; inbound Email Routing remains unmetered apart from Worker processing. Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations. Check current Cloudflare pricing before deploying.

        Is EmailFlare open source?+

        The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/0xdps/emailflare/d448ea205937a5e82683f89013d4c23159da5b7d/LICENSE. Source code and contributor credit are available at https://github.com/0xdps/emailflare.

        Discussion · 0

        sign in to comment →
        No comments yet — be the first.