Deploy
Set up Flaresend in your Cloudflare account, from an empty account to your first API key.
This is the one-time setup. Run the commands from apps/mailer unless a step says otherwise. Replace acme.com, <ZONE_ID> and mailer.example.com with your own values.
Log in to Cloudflare
npx wrangler loginCreate the resources
npx wrangler d1 create flaresend
npx wrangler r2 bucket create flaresend-payloads
npx wrangler r2 bucket lifecycle add flaresend-payloads --prefix payloads/ --expire-days 30
npx wrangler queues create flaresend-send
npx wrangler queues create flaresend-events
npx wrangler queues create flaresend-dlq
npx wrangler queues create flaresend-webhookswrangler d1 create prints a database_id. Paste it into d1_databases[0].database_id in both wrangler.jsonc and wrangler.dev.jsonc.
The lifecycle rule deletes email bodies from R2 after 30 days. After that, GET /v1/emails/:id/content returns 404 content_expired and the email can no longer be resent. The metadata in D1 stays.
Onboard each sending domain
Repeat this for every domain a project will send from. On zones that use Cloudflare DNS, it adds the SPF and DKIM records for you.
npx wrangler email sending enable acme.com
npx wrangler email sending dns get acme.com # confirm the records
npx wrangler email sending listAdd a DMARC record for the domain too, unless the onboarding already added one:
_dmarc.acme.com TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@acme.com"Once the mailer is deployed with a CF_API_TOKEN that has the Edit permissions listed in Configuration, the dashboard can do this step and the next one for you: Add domain onboards a new domain, and Set up in Cloudflare does it for a domain already on the project. The CLI and API equivalent is Set up a domain.
Send delivery events to the events queue
One subscription per sending domain. --zone-id is the zone that contains the domain.
npx wrangler queues subscription create flaresend-events \
--source email.sending \
--events message.delivered,message.deferred,message.bounced,message.failed,message.rejected,message.complained \
--zone-id <ZONE_ID> --domain acme.com \
--name flaresend-acme-comWithout this subscription emails still send, but they stay at sent forever: no delivered, bounced or complained status, and no automatic suppressions.
Set the secrets
npx wrangler secret put ADMIN_API_KEY # generate with: openssl rand -base64 32
npx wrangler secret put TRACKING_SECRET # open/click tracking and unsubscribe links: openssl rand -base64 32
npx wrangler secret put CF_API_TOKEN # optional: domain status and domain setupIf you set CF_API_TOKEN, also set the CF_ACCOUNT_ID var in wrangler.jsonc. Configuration lists the token's permissions.
Set the public URL
In wrangler.jsonc, set vars.PUBLIC_BASE_URL to the URL the mailer is reachable at, for example https://mailer.example.com. Tracking pixels, click links and unsubscribe links point there.
To serve the mailer on your own hostname instead of workers.dev, add a route:
"routes": [{ "pattern": "mailer.example.com", "custom_domain": true }]Migrate and deploy
npx wrangler d1 migrations apply flaresend --remote
npx wrangler deployCheck that it answers. /health runs a D1 query and returns { "ok": true }:
curl https://mailer.example.com/healthFrom the repo root, pnpm run deploy does both steps (migrate:remote, then deploy in apps/mailer).
Create the first project and API key
curl -X POST https://mailer.example.com/v1/admin/projects \
-H "Authorization: Bearer $FLARESEND_ADMIN_KEY" -H "Content-Type: application/json" \
-d '{"slug":"acme","name":"Acme","defaultFrom":"Acme <hello@acme.com>","allowedDomains":["acme.com"]}'
curl -X POST https://mailer.example.com/v1/admin/projects/acme/api-keys \
-H "Authorization: Bearer $FLARESEND_ADMIN_KEY" -H "Content-Type: application/json" \
-d '{"name":"production","mode":"live"}'The second call returns the full key once. Store it in your app's secrets: Flaresend keeps only the first 12 characters and a SHA-256 hash.
Or with the CLI:
export FLARESEND_BASE_URL=https://mailer.example.com FLARESEND_ADMIN_KEY=...
flaresend projects create --slug acme --name Acme --domains acme.com --default-from "Acme <hello@acme.com>"
flaresend keys create --project acme --name production --mode liveSend a test email
curl -X POST https://mailer.example.com/v1/emails \
-H "Authorization: Bearer $FLARESEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"you@your-inbox.com","subject":"It works","text":"Sent through Flaresend."}'from can be left out because the project has a defaultFrom. Then follow the Quickstart.
Optional next steps
- Deploy the dashboard.
- Set up CI so pushes to
mainmigrate and deploy. - Let other Workers send over RPC: add a service binding to
flaresend, entrypointMailerRpc. See Cloudflare Workers.