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

  1. Wait until the health check passes. Liveness is /healthz. Readiness is /readyz, which also looks at the worker heartbeat.
  2. Open https://YOUR_DOMAIN/setup/BOOTSTRAP_TOKEN once. The token is a service variable. Create the first admin there.
  3. In Admin (/admin/), create users and mailboxes on MAIL_DOMAIN.
  4. In Resend, verify that domain and publish the DNS records Resend shows you. The app registers its inbound webhook when PUBLIC_URL is a public origin. The template sets PUBLIC_URL from Railway’s public domain.
  5. 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

Deploy on Railway

Markdown version of this article