Transactional email setup
Email carries sign-in links, password recovery and application messages. Verify it before depending on emailed authentication. The Worker and container use different transports; a verified sender alone does not prove delivery.
Cloudflare Worker: Amazon SES
- Choose the AWS region that will send mail. Create and verify the sending domain/identity there, publish its DNS authentication records, and configure the intended configuration set and delivery-event destinations. Follow SES identity verification.
- Check sandbox status in that region. Sandbox accounts restrict recipients; identity verification does not remove that restriction. Use verified recipients or the mailbox simulator for testing, then request production access through AWS. SES regional requirements apply independently to failover regions.
- Provide the runtime's AWS credentials with the required SES sending permission for that identity. Store
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYand, for temporary credentials,AWS_SESSION_TOKENas GitHub environment secrets. Arrange rotation before temporary credentials expire. - Set GitHub environment variables
SES_REGION,SES_FROMand, if used,SES_CONFIGURATION_SET. The first four required values are the access key, secret key, region and sender; a partial SES configuration is rejected. - Deploy through CI. Confirm queue/scheduler bindings and an approved organization sender, then send one controlled message to an operator-owned inbox. Check actual inbox receipt and provider delivery/bounce evidence as well as application queue state.
The Worker also supports Mailgun: MAILGUN_API_KEY is a secret, with MAILGUN_DOMAIN, MAILGUN_REGION and MAIL_FROM as deployment variables. Configure exactly one Worker transport. The secret renderer rejects simultaneous SES and Mailgun configuration; it does not select a provider silently.
Container: SMTP
Set SMTP_URL and SMTP_FROM in the private container environment. Use your provider's authenticated SMTP endpoint, credentials and TLS requirements; see container setup. If SES provides the SMTP endpoint, use its SMTP credentials rather than treating the Worker AWS API credentials as interchangeable with an SMTP password.
Restart/redeploy the container after configuration changes. Verify its scheduler is running so queued messages are processed. Test inbox delivery with an operator-owned recipient and confirm the configured sender is the one the organization intends to use.
Authentication and sender checks
Email sign-in also requires MAGIC_LINK_SIGNING_KEY; configuring a transport alone does not enable every authentication route. Check Ways in and retain a working administrator recovery method while testing. For Apple relay recipients, follow Apple sender registration too.
Where organization sender approval/attestation applies, changing the deployment's sending address can invalidate the old approval. Review the sender state rather than retrying delivery indefinitely. The public demo can further restrict recipient domains; an application acceptance is not proof that demo mail was sent externally.
| Symptom | Check |
|---|---|
| Provider accepts but inbox is empty | Delivery/bounce events, recipient suppression, spam folder and sender authentication |
| SES rejects an address | Region, verified identity, sandbox recipient restriction, permissions and sending status |
| Messages stay queued | Queue consumer/scheduler, retries, dead-letter state and provider error |
| SMTP authentication fails | Correct SMTP credential type, endpoint, port and TLS settings |
| Mail stopped after credential rotation | New values actually deployed; temporary AWS session token rotated with its key pair |
For a transport change, review queued mail, prepare and verify the new sender, then switch the complete configuration through CI. Preserve message/audit history and account for retries before sending manual replacements. Removing a transport will affect login recovery and queued messages; it is an operational change, not just a UI preference.
Implementation reference: scripts/render-deploy-secrets.mjs, entries/worker/src/runtime.ts, entries/container/src/config.ts, packages/mailer/.