Coolify is a self-hostable PaaS. Dispatch ships a docker-compose.yml that works on Coolify as-is: Coolify runs the three services, puts its proxy (Traefik or Caddy) in front of the app, and issues the TLS certificate.
You need about 10 minutes, a server connected to Coolify v4, and a domain you can point at it.
- 1. Point your domain at the server
- 2. Create the resource
- 3. Set the domain and DOMAIN
- 4. Deploy and run the setup wizard
- Persistent storage
- Updating
- Backups
- Troubleshooting
Create a DNS record for the hostname Dispatch will use, for example mail.example.com:
| Type | Name | Value |
|---|---|---|
A |
mail |
IPv4 address of your Coolify server |
AAAA (optional) |
mail |
IPv6 address of your Coolify server |
If you use Cloudflare, "DNS only" (grey cloud) is the simplest choice while Coolify gets the certificate. You can turn the proxy on afterwards with SSL mode Full (strict).
Pick one of the two ways. Both use the same compose file.
The compose file stays in sync with the repository, and Coolify can redeploy when it changes.
- In your project, click + New and choose Public Repository.
- Repository URL:
https://github.com/codextde/dispatch, branchmain. - Build pack: Docker Compose. Base directory
/, Docker Compose location/docker-compose.yml. - Click Continue. Coolify reads the compose file and shows the services
app,workeranddb.
Nothing is built on your server: the compose file uses the prebuilt image ghcr.io/codextde/dispatch, so Coolify only pulls it.
- In your project, click + New and choose Docker Compose Empty.
- Paste the contents of
docker-compose.ymland save.
Domain of the app service. Open the app service settings (Option A: Configuration → General → Domains for app; Option B: the settings of the app service) and enter your domain with the container port:
https://mail.example.com:3000
The :3000 suffix is not part of the public URL. It tells Coolify's proxy which container port to route to; visitors still use https://mail.example.com. Leave the domains of worker and db empty. They are internal.
If your server has a wildcard domain configured, Coolify may have prefilled a generated domain such as
http://app-abc123.example.com:3000. Replace it with yours.
Environment variable. Under Environment Variables, set:
| Name | Value |
|---|---|
DOMAIN |
mail.example.com (hostname only, no https://) |
DISPATCH_VERSION |
optional: pin a release such as 1.0.0 (default latest) |
DOMAIN is the only setting Dispatch needs. If you leave it empty, Dispatch falls back to the domain Coolify assigned to the app service (the SERVICE_FQDN_APP_3000 magic variable). Setting it explicitly is more reliable, because some Coolify versions have kept stale generated values in magic variables after a domain change.
Don't add a database password or secrets. The database password is generated by the db container on first boot, and Dispatch generates its encryption key in its data volume.
- Click Deploy. The first deployment pulls the images, starts Postgres, runs the database migrations and starts the app and worker. It usually takes a minute or two.
- Wait until all three services are running and healthy.
- Open
https://mail.example.com/setupand follow the first-run wizard. - At the Owner account step, the wizard asks for the one-time setup code. Open the
appservice in Coolify, go to its Logs tab, and look for the box with the lineDispatch first-run setup code: XXXX-XXXX-XXXX. It's printed on start and again when/setupis opened, until an owner exists. Only someone with access to your Coolify dashboard can claim the instance. The wizard then creates your super-admin account and walks you through the instance settings. See Self-hosting → First-run setup.
Until you configure email delivery, emails such as sign-in links are printed to the logs instead of being sent. In Coolify, open the app service and look at its Logs. Then set up SMTP or Amazon SES in Admin → Settings → Email: Email delivery.
Coolify creates the three named volumes from the compose file. You'll find them under Persistent Storage, prefixed with the resource ID:
| Volume | Mounted at | Contains |
|---|---|---|
dispatch-db |
db:/var/lib/postgresql/data |
The PostgreSQL database: all conversations, settings, users |
dispatch-data |
app,worker:/data |
Attachments (when using local storage) and the master encryption key in /data/secrets |
dispatch-secrets |
db:/secrets, app,worker:/secrets (read-only) |
The generated database password |
Deleting the resource with "delete volumes" deletes all data. Back up dispatch-db and dispatch-data together: the encryption key in dispatch-data is needed to decrypt the mailbox credentials stored in the database.
Dispatch applies database migrations automatically when the new version starts.
- Pinned version (recommended): set
DISPATCH_VERSIONto the new release (see releases) and click Redeploy. A new tag always makes Coolify pull the new image. - Tracking
latest: redeploy and make sure Coolify pulls the image again instead of reusing the cachedlatest(depending on your Coolify version, via the pull latest images option of the redeploy/restart action).
Read the upgrade notes before jumping major versions, and take a backup first.
- Option B (Docker Compose Empty): Coolify recognizes the
dbservice as PostgreSQL. Open it and configure Backups to create scheduled dumps, optionally uploaded to S3-compatible storage. - Option A or any setup: add a Scheduled Task on the
dbcontainer. For example, a dailypg_dumpis described in Backup & restore.
Either way, also back up the dispatch-data volume, or at least /data/secrets/master.key. Without it the dump cannot decrypt stored mailbox passwords and OAuth tokens.
| Symptom | Fix |
|---|---|
404 page not found / no available server |
The app domain must include :3000, e.g. https://mail.example.com:3000. Redeploy after changing it. |
| Certificate errors | DNS must point to the server before the first deploy. With Cloudflare, use "DNS only" until the certificate is issued. |
| Links in emails point to the wrong host | Set DOMAIN explicitly and redeploy. |
worker waits forever |
The worker starts after app is healthy. Check the app logs, usually for a database connection problem. |
| Health check | https://mail.example.com/api/health returns {"status":"ok",...} when the app and database are up. |
More in Self-hosting → Troubleshooting.