Mail hq

Documentation

Security & limits

How Mail HQ protects personal email, how to operate it safely, and what it does not do yet.

Overview

Mail HQ serves personal email. Keep the dashboard behind Cloudflare Access and the R2 bucket private.

Dashboard

Cloudflare Access login, verified again by the Worker on every request

Agents

Hashed, expiring, revocable tokens scoped to one mailbox

Storage

Private R2 for mail content; encrypted provisioning credential

Dashboard authentication

  • The Worker validates the Access application JWT itself: signature, issuer, audience, expiry, and user-session claims, independently of the edge policy.
  • Requests must arrive on the exact APP_ORIGIN, and mutations from another origin are rejected.
  • Access policies decide who can use the dashboard. There is no second hardcoded email allowlist.
  • Dashboard responses are sent with no-store caching, nosniff, no-referrer, and X-Frame-Options: DENY.

Agent tokens

MCP tokens are independent, random, hashed at rest, limited to one mailbox, scoped to read and/or send, expiring, and revocable. A read-only token does not authorize changing mailbox state, and read_mail does not mark messages as read. Never publish tokens or put them in URL query strings.

Provisioning credential

The optional Cloudflare provisioning token is AES-GCM encrypted in R2 with CONFIG_ENCRYPTION_KEY. Keep that key stable and backed up privately, and restrict the token to the intended zones and permissions. No normal app endpoint overwrites existing MX records or email routing rules.

Untrusted mail

Received mail is untrusted input, including when it is returned to an agent over MCP. Inbound HTML is shown in a sandboxed frame with scripts, forms, and remote resources blocked. Review agent permissions before allowing an agent to send.

Sending and delivery

StatusMeaning
Sent to CloudflareThe provider accepted the submission. Not proof of delivery or inbox placement
FailedA definitive rejection was recorded
UncertainSubmission may have happened but confirmation was lost. Check before retrying

Retries of a send must reuse its idempotency key. Ambiguous failures are not retried automatically, and a temporary SMTP 4xx response can be retried by the provider, so do not submit another copy yourself. A provider-accepted message may still bounce or be filtered into spam.

When investigating placement, read the recipient's original headers (SPF, DKIM, DMARC alignment, TLS, envelope sender, Message-ID, Date, MIME structure) and check Cloudflare sending events for deferrals or bounces. Passing SPF, DKIM, and DMARC does not guarantee the inbox.

Backups

Retain database and object-storage backups together: message metadata in D1 refers to bodies and attachments in R2 by object key. Rolling back Worker code does not roll back D1 or R2. Trash is reversible, and there is no automatic permanent-deletion job.

Keep real email samples, account records, private keys, environment files, generated deployment bundles, and test-recipient logs out of Git.

Current limits

LimitValue
Outgoing email size5 MiB, including attachment encoding
Recipients per message50 combined (to, cc, bcc)
Dashboard attachment upload3 MiB per attachment
Incoming email size25 MiB
MCP attachment download2 MiB
MCP requests120/min per token, 240/min per client IP
Sends30/min per mailbox
Agent token lifetime1–365 days (dashboard offers 7, 30, 90, 365)

Not supported yet

  • Search covers subject, sender, and preview text only.
  • External historical mail is not imported automatically.
  • IMAP/SMTP access is planned, not implemented.
  • Push notifications and offline support are not implemented.