cMail docs

Running cmail

The repeatable jobs for a live deployment, in one place: upgrading, backing up, restoring, rolling back, watching for trouble, and rotating secrets. Most procedures already live in Deployment and verification; this page gives the order and links to them, and adds monitoring and secret rotation. Tick off the Operations checklist as you go.

Examples use the default resource names (cmail-web, cmail-db, cmail-storage, cmail-backup). Use your own.

Upgrade

  1. Start from a clean checkout (git status --short is empty) and read the changelog entries since your version. Note any new migration, binding, cron or secret.
  2. Your wrangler.toml files are ignored by Git, so a pull never changes them. Compare each with its wrangler.toml.example and copy across any new binding, variable or cron by hand.
  3. Record the current Pages deployment ID and email Worker version (see Roll back).
  4. Take a fresh backup point (see Back up). If a migration is pending, also record a D1 Time Travel bookmark (see Restore).
  5. Follow Updating an existing instance: pull, pnpm install --frozen-lockfile, pnpm release:check, pnpm db:migrate, then pnpm deploy (the email Worker, then Pages). pnpm deploy doesn't cover the backup Worker or the landing page: if they changed, redeploy them too (pnpm exec wrangler deploy in apps/backup-worker, and pnpm deploy:landing).
  6. Smoke-test: the home page loads, /mail redirects when signed out, a test account can sign in, and a test message arrives and opens. Then work through "After deployment" in the operations checklist.
  7. If anything fails, roll back straight away, then investigate.

Deploy one instance at a time when you run several, and finish verifying each before starting the next.

Back up

Rehearse a restore on preview or recovery resources before relying on any of these, and again after changing the backup setup.

Restore

Pick the smallest restore that fixes the problem:

What went wrong Restore Where
One message body or attachment deleted or damaged Copy the object back from the backup bucket Automated backups
Bad data change in D1 (a migration, bulk action or script) D1 Time Travel to a bookmark before the change Point-in-time recovery
D1 database lost, or the change is outside the Time Travel window Rebuild from the NDJSON dump or a D1 export into a new database Automated backups, Back up and rehearse recovery
Whole account lost Restore the off-account D1 export and R2 copy together into new resources Back up and rehearse recovery

A restore of live data rewinds or replaces mail people may have read since. Get approval, pause Email Routing and interactive writes first, and compare row counts before re-enabling routing. If the incident touched both D1 and R2, restore both to the same point.

Roll back

A bad code release is the most likely incident. Roll Pages back first, then the email Worker, using the IDs you recorded before deploying. Don't roll code back past a D1 migration unless the older code works with the newer schema; deploy a forward fix instead. Full procedure: Roll back a release.

Monitor

cmail has no built-in alerting. These are the places to look, and what should make you act.

Uptime. An external check every few minutes:

Scheduled jobs. Check each Worker's Cron Trigger past events in the Cloudflare dashboard, or its logs:

Worker Schedule A healthy run Act when
Email Worker: retention 17 3 * * * (only if RETENTION_JOBS_ENABLED) Completes, with audit events Retention run failed, or backlog remaining on several days in a row
Email Worker: link list 7 * * * * Shards update, or nothing changed Link blocklist refresh failed or kept the previous copy for more than a day (links still open; checks go stale after two days). On Workers Free every refresh fails by design: the feature needs Workers Paid
Backup Worker 23 * * * * Run succeeds; the day's manifest says complete: true; d1/<date>/complete.json exists Any failed run. A paused run means objects vanished from the mail bucket: check the SOURCE binding before acknowledging (see Automated backups)

Mail flow.

Growth. D1 size and R2 object count in the dashboard, and mailbox quota denials in the trace. The backup Worker suits up to tens of thousands of mail objects.

Logs hold no message content by design. Keep it that way when you add your own monitoring: don't log subjects, bodies or full addresses.

Rotate secrets

Rotate on a schedule your organisation sets, and immediately if a value is printed, committed or shared. Generate a new value, store it, redeploy, then check sign-in and mail flow. Every secret is per environment: rotating production never touches preview.

Secret Where What rotating it does How
SESSION_SECRET Pages Signs everyone out. Also ends sign-ins in progress, unused email sign-in codes and bootstrap proofs; message links signed earlier show the "link not verified" page until the message is opened again. MCP connections are unaffected Plan it as a sign-out event: tell users, set the new value, redeploy Pages
GOOGLE_CLIENT_SECRET, MICROSOFT_CLIENT_SECRET Pages None for signed-in users; sign-in breaks if the provider and cmail disagree Add a new secret at the provider, set it in cmail, redeploy, test a sign-in, then delete the old secret at the provider
TURNSTILE_SECRET_KEY Pages Code-request bot checks fail until both match Rotate in the Turnstile dashboard, set the new value, redeploy
INBOUND_SENDER_HASH_KEY Email Worker Resets per-sender rate-limit buckets (a one-hour limit reset) Generate 32 random bytes as unpadded base64url, wrangler secret put on the Worker
VAPID_PRIVATE_KEY with VAPID_PUBLIC_KEY Pages and Email Worker (private); both [vars] (public) Existing browser subscriptions stop working. Each browser re-subscribes the next time someone opens cmail with alerts on Rotate the pair together in both runtimes, deploy the Worker then Pages
POSTMARK_API_KEY, CLOUDFLARE_EMAIL_API_TOKEN Pages Outbound fails while the provider and cmail disagree Create the new key, set it, redeploy, send a test, then revoke the old key
CLOUDFLARE_SUPPRESSIONS_API_TOKEN Pages Unblock and send again is unavailable until the new token is set Create a new account-owned token with Email Sending: Edit only, set it, redeploy, then delete the old token
BOOTSTRAP_ADMIN_TOKEN, BOOTSTRAP_ADMIN_EMAIL Pages Should not exist after the first manager is set up If still present, delete both (see Bootstrap)

Also review who holds Cloudflare, identity-provider and DNS access, and remove anyone who no longer needs it.

← Documentation home