Home / Guides / How to deploy Mailstead on Railway
How to deploy Mailstead on Railway
Use the published template if you want the path this project documents: Deploy Mailstead. It creates one service from the repository image, a volume mounted at /data, and generated secrets. It does not verify your domain, create mailboxes, or send a test message. Those remain after the container is up.
You need a Resend API key with access to send and receive, a domain whose DNS you can edit, and an address for admin password recovery. Postgres and Web Push are optional.
After the template finishes
- Wait until the health check passes. Liveness is
/healthz. Readiness is/readyz, which also looks at the worker heartbeat. - Open
https://YOUR_DOMAIN/setup/BOOTSTRAP_TOKENonce. The token is a service variable. Create the first admin there. - In Admin (
/admin/), create users and mailboxes onMAIL_DOMAIN. - In Resend, verify that domain and publish the DNS records Resend shows you. The app registers its inbound webhook when
PUBLIC_URLis a public origin. The template setsPUBLIC_URLfrom Railway’s public domain. - Confirm the webhook is present, then send mail to a mailbox and reply from it.
The start command in a manual service is python -m mailstead.run. It migrates, then runs one gunicorn worker. Jobs run in that same process. Set RAILWAY_DEPLOYMENT_DRAINING_SECONDS=30 on a manual service. The template’s variable list is in deploy-railway.md.
What has to stay attached
The volume at /data holds SQLite and mail blobs unless you set DATABASE_URL for Postgres. Removing the volume wipes that store. Hobby’s platform limits include a 5 GB maximum volume and two custom domains (Railway pricing, checked 2026-10-14). The Railway-provided *.up.railway.app hostname is enough for PUBLIC_URL while you test. Your mail domain is the Resend domain, which can be different from the hostname people use to open the app.
Required variables: RESEND_API_KEY, MAIL_DOMAIN, ADMIN_RECOVERY_EMAIL, plus generated SECRET_KEY, APP_ENCRYPTION_KEY, and BOOTSTRAP_TOKEN. Web Push needs VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY. Without them, the app still runs; push stays off.
Send and receive checklist
- Health check is green.
- You can sign in as the admin you created.
- Resend shows the domain as verified.
- A webhook points at
https://YOUR_PUBLIC_URL/hooks/resend. - A message from an outside account arrives in the mailbox, not only in Resend’s logs.
- A reply leaves through Resend and arrives at the outside account.
/ops/shows storage and the worker. The usage panel shows Resend quota, which needs an API key that can read usage.
If inbound mail never appears, check PUBLIC_URL, the webhook signing setup, and that the recipient is a mailbox on MAIL_DOMAIN. If outbound mail fails, check the API key and the Resend quota. Received messages count toward that quota. See Resend for a team inbox.
Day two
Redeploying runs migrations on start and does not reset the bootstrap token. Backups are a volume backup plus python manage.py export for configuration and metadata, not a blob export. Details: operations.
This marketing site is not the webmail service. Deploying Mailstead does not publish mailstead.app, and mailstead.app will not show your mail.
Sources
- Repository, docs/deploy-railway.md and README.
- Railway, pricing and plan limits, checked 2026-10-14.
- Resend, pricing, inbound mail counts toward the transactional quota, checked 2026-10-14.