Dashboard
Deploy the admin dashboard as its own Worker, protected by Cloudflare Access.
The dashboard (apps/dashboard) is a Next.js app deployed as the Worker flaresend-dashboard with @opennextjs/cloudflare. It has no login of its own: Cloudflare Access is the login, and the dashboard's middleware checks the Access token again on every request.
It never holds an API key. Every read and write goes to the mailer's AdminRpc entrypoint over a service binding, and all of those calls run on the server.
"services": [
{ "binding": "MAILER_ADMIN", "service": "flaresend", "entrypoint": "AdminRpc" }
]What you can do in it is described in Dashboard.
Deploy
Deploy the mailer first
The MAILER_ADMIN binding points at the Worker flaresend, so it has to exist. See Deploy.
Create the Access application
- In the Zero Trust dashboard, go to Access → Applications → Add an application → Self-hosted.
- Application domain: the hostname the dashboard will use, for example
dashboard.acme.com. - Add a policy, for example Action Allow, Include Emails = your address, or an email domain. Login methods: one-time PIN or Google.
- Save, open the application's overview and copy the Application Audience (AUD) Tag.
- Find your team domain under Settings → Custom Pages (or Settings → General). It looks like
<team>.cloudflareaccess.com.
Put the Access values in wrangler.jsonc
"vars": {
"ACCESS_AUD": "<the AUD tag>",
"ACCESS_TEAM_DOMAIN": "<team>.cloudflareaccess.com"
}Also set account_id to your account.
Add the hostname
Serve the dashboard on the same hostname you gave the Access application:
"routes": [{ "pattern": "dashboard.acme.com", "custom_domain": true }]custom_domain: true creates the DNS record and the certificate.
Deploy
pnpm --filter @flaresend/dashboard run deployThis runs opennextjs-cloudflare build && opennextjs-cloudflare deploy. The dashboard uses no cache bindings (every page is dynamic), so there is nothing else to create.
Building on Windows
opennextjs-cloudflare build fails on Windows with EPERM: operation not permitted, symlink unless your account may create symlinks (Developer Mode on, or an elevated shell). Build in WSL or CI, or turn on Developer Mode. next dev and next build are not affected.
How the Access check works
apps/dashboard/src/middleware.ts runs on every request, including /_next assets that go through the Worker:
ACCESS_AUD and ACCESS_TEAM_DOMAIN | Build | Result |
|---|---|---|
| Both set | any | Requires the Cf-Access-Jwt-Assertion header (403 without it) and verifies it against https://<team>/cdn-cgi/access/certs, issuer https://<team>, audience ACCESS_AUD (403 if invalid). |
| Either empty | next dev (NODE_ENV=development) | No check. |
| Either empty | production | 500 "Cloudflare Access is not configured for this dashboard". |
Static files under /_next/static are served straight from the assets binding and skip the middleware. They contain no data.
Run it locally
The quickest way is next dev against the mailer's HTTP admin API. Start the mailer (pnpm --filter @flaresend/mailer dev, on http://localhost:8787), then create apps/dashboard/.dev.vars:
MAILER_URL=http://localhost:8787
MAILER_ADMIN_KEY=<the mailer's ADMIN_API_KEY>pnpm --filter @flaresend/dashboard devWhen both are set and NODE_ENV is not production, the dashboard uses an HTTP client for /v1/admin/* instead of the binding. In production, the binding always wins when it exists.
Keep the admin key out of .env
Don't put these in .env or .env.local. opennextjs-cloudflare build bundles .env, .env.local and .env.production* into the Worker, so the admin key would ship inside it. .dev.vars* and .env.development* are not bundled.
To test with the real service binding, run the mailer with wrangler dev in one terminal and pnpm --filter @flaresend/dashboard preview in another. Wrangler connects MAILER_ADMIN to the local flaresend Worker. This is a production build, so with the Access vars empty every request gets a 500.
Checks
pnpm --filter @flaresend/dashboard typecheck
pnpm --filter @flaresend/dashboard test
pnpm --filter @flaresend/dashboard buildAfter changing apps/dashboard/wrangler.jsonc, run pnpm --filter @flaresend/dashboard cf-typegen to regenerate worker-configuration.d.ts.