FlaresendDocs

Configuration

Every variable, secret and binding the mailer Worker reads, and what changes when you set it.

The mailer's configuration lives in apps/mailer/wrangler.jsonc (deployed) and apps/mailer/wrangler.dev.jsonc (pnpm dev). Secrets are set with wrangler secret put and never go in either file.

Variables

Set in vars in wrangler.jsonc.

VariableDefault in the repoWhat it does
ENVIRONMENTproductionAnything other than production turns on POST /v1/admin/dev/events, the route that fakes Cloudflare delivery events. wrangler.dev.jsonc sets development.
PUBLIC_BASE_URLthe workers.dev URLThe mailer's public URL, without a trailing slash. Used to build open-tracking pixels (/t/o/…), click links (/t/c/…) and unsubscribe links (/u/…).
BROADCAST_MAX_RECIPIENTS500The most subscribed contacts a broadcast may go to. A value that isn't a positive number falls back to 500.
CF_ACCOUNT_IDyour account IDNarrows the zone lookup (GET /zones?name=…&account.id=…) when the mailer checks or sets up a sending domain with CF_API_TOKEN. Optional; without it the lookup searches every zone the token can see.

Secrets

SecretRequiredWhat it does
ADMIN_API_KEYYes, for admin accessThe Bearer token for every /v1/admin/* route and for the CLI. When it is not set, every admin request fails with 401 invalid_api_key. Generate it with openssl rand -base64 32.
TRACKING_SECRETFor tracking and unsubscribeSigns open-tracking tokens and unsubscribe links. Without it, open tracking is skipped (a warning is logged), broadcasts go out without List-Unsubscribe headers, and /u/:token pages answer "Link not valid". Click tracking does not need it.
CF_API_TOKENNoA Cloudflare API token for domain status and domain setup. Sending never uses it: sending goes through the EMAIL binding. Without it, every domain's status is unknown and domain setup fails with 422 cf_token_missing.

Set them on the deployed Worker:

cd apps/mailer
npx wrangler secret put ADMIN_API_KEY
npx wrangler secret put TRACKING_SECRET
npx wrangler secret put CF_API_TOKEN

For local development, put them in apps/mailer/.dev.vars (copy .dev.vars.example):

apps/mailer/.dev.vars
ADMIN_API_KEY=dev-admin-key
TRACKING_SECRET=dev-tracking-secret
CF_API_TOKEN=

CF_API_TOKEN

Create it in the Cloudflare dashboard under My Profile → API Tokens → Create Token → Create Custom Token. Under Zone Resources, pick All zones from an account and your account, so new domains work without editing the token.

PermissionWhy
Zone → Zone → ReadGET /zones?name=… finds the zone that holds the domain
Email Sending → ReadReads the domain's sending status
Email Sending → EditSet up in Cloudflare adds the domain to Email Sending
DNS → EditSet up in Cloudflare adds the SPF, DKIM, return-path and DMARC records
Queues → EditSet up in Cloudflare subscribes the domain's delivery events to flaresend-events

With only the two Read permissions, the status check works and setup doesn't. Domain setup only adds missing records. It never changes a DNS record that already exists; a clashing record is reported as a conflict for you to fix by hand. The DMARC record it adds is v=DMARC1; p=none, and only when neither the domain nor the zone apex already has one.

Email Sending → Edit also allows sending mail from any onboarded domain in the account. Treat this token like a password.

Domain status is cached in the domain_status table for 10 minutes. GET /v1/domains?refresh=1 checks again right away.

Bindings

BindingTypePoints at
EMAILsend_emailCloudflare Email Service. Don't add allowed_sender_addresses: senders are restricted per project in code.
DBD1database flaresend, migrations in apps/mailer/migrations
PAYLOADSR2bucket flaresend-payloads
SEND_QUEUEQueue producerflaresend-send
WEBHOOK_QUEUEQueue producerflaresend-webhooks
RATE_LIMITERRate limitingnamespace 1001, 300 requests per 60 seconds

The mailer never produces to flaresend-events: Cloudflare puts delivery events there through the queue subscription you created during deployment.

Queue consumers

Queuemax_batch_sizemax_batch_timeoutmax_retriesOtherDead letter queue
flaresend-send102 s8max_concurrency: 5flaresend-dlq
flaresend-events505 s10flaresend-dlq
flaresend-webhooks102 s8flaresend-dlq
flaresend-dlq500

max_concurrency: 5 on the send queue limits how many consumer invocations send at the same time. wrangler.dev.jsonc leaves it out. What each consumer does with failures is in Architecture.

Rate limiting

apps/mailer/wrangler.jsonc
"ratelimits": [
  { "name": "RATE_LIMITER", "namespace_id": "1001", "simple": { "limit": 300, "period": 60 } }
]

This is a safety valve per project: every live send counts against the key project.id. Over the limit, the API returns 429 rate_limited with Retry-After: 60. period can only be 10 or 60. Test-key sends don't count.

The per-project daily limit is separate. It is stored on the project (dailyLimit, 0 means no limit), counted in D1 per UTC day, and returns 429 daily_limit_exceeded when used up.

Cron

apps/mailer/wrangler.jsonc
"triggers": { "crons": ["*/5 * * * *"] }

Every 5 minutes the mailer queues scheduled emails due in the next 12 hours, sends the next chunk of every running broadcast, and rolls up yesterday's analytics once a day. See Architecture.

Other settings

SettingValueWhy
compatibility_date2026-09-01
compatibility_flagsnodejs_compatNeeded by React Email rendering
aliasprettier/standalone and prettier/plugins/html → src/stubs/prettier.tsKeeps prettier, which @react-email/render imports but never uses, out of the bundle
observability.enabledtrueWorkers Logs for console.log output
workers_devtrueThe Worker is also reachable on workers.dev

On this page