Skip to content

Troubleshooting

Mail not arriving, sending failing, auth 404s, and where to look.

Updated View as Markdown

Common failure modes and where to check. The onboarding health view on /admin/domains surfaces domain and DNS state live from Cloudflare.

Mail not arriving

Is the domain active?

On /admin/domains, the domain must show active. If it’s stuck at pending_nameservers, your registrar isn’t pointing at the assigned Cloudflare nameservers yet. Hit Refresh after DNS propagates.

Is the catch-all rule set?

Doota points the Email Routing catch-all at MAIL_IN_WORKER_NAME. Confirm that variable is the deployed inbound Worker’s name (doota-mail-inbound), and that Email Routing is enabled on the zone. Re-running onboarding is idempotent — safe to retry.

Is MX present and correct?

Check the zone’s DNS for Cloudflare’s Email Routing MX records. If your DNS is external, add the records shown on the domain screen.

Tail the inbound Worker

wrangler tail doota-mail-inbound

A message to an unknown or disabled recipient is rejected cleanly and stores nothing — check the local part actually maps to a mailbox or active alias.

Sending failing

  • Domain not active — outbound needs a working sending path. Provisioning and sending are blocked until the domain is active.
  • DKIM missing — the sending domain/subdomain must have DKIM + DMARC + return-path records. For external DNS, add the records from the domain screen.
  • Rate limited — sends are counted per scope; a burst can hit the send counter. Check doota-mail-jobs logs.
  • Recipient suppressed — a prior hard bounce or complaint adds the address to the suppression list, and later sends to it are dropped. Remove the suppression if it was transient.
  • Tail the jobs Worker:
    wrangler tail doota-mail-jobs
    The submission status (queued → sending → sent → delivered → bounced) tells you how far a message got.

Auth routes return 404 (/api/auth/*)

The URL the app is served on isn’t in ORIGINS. better-auth only answers for origins in that list (full URLs, with protocol). In dev, include http://localhost:5173 (the default dev port); in production, your real app URL(s). Redeploy after changing.

Can’t decrypt messages / search returns nothing

The three Workers don’t share the same keys. MAIL_DEK and MAIL_SEARCH_KEY must be identical on doota, doota-mail-inbound, and doota-mail-jobs. Re-set them everywhere and redeploy. If MAIL_DEK decodes to the wrong length the Worker throws MAIL_DEK must decode to 32 bytes — regenerate with openssl rand -base64 32.

Locked out of the super-admin

Use the CLI recovery floor — no email or network needed:

pnpm --filter doota reset-admin you@external-example.com 'new-password' --remote --clear-2fa

See Operations → Recovery hatches.

Live ticks (delivery/read status) not updating

The MailEventHub Durable Object lives in doota-mail-jobs; the web and inbound Workers bind to it by script_name. If ticks stall, confirm doota-mail-jobs is deployed and its DO migration applied — deploy it first on a cold account.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close