Run CI on the self-hosted GitLab See merge request coffey-labs/stalwart-migrator!1
stalwart-migrator
In-place upgrade tool for Stalwart Mail Server, 0.15.5 → 0.16: no data loss, a checkpoint at every step so an interrupted run resumes instead of restarting, and automated validation that the server still works afterwards. Go, standard library only.
A companion to ihasmail, a JMAP-first webmail client for Stalwart. That one is what you read your mail in; this one gets the server underneath it onto a version that speaks the protocol it needs.
Caution
A migration cannot be undone, and this tool does not undo one. Recovery from a failed migration is your own snapshot or backup, taken beforehand and checked.
runwill not start until you confirm you have one. See Recovery.
Documentation
| 📘 Upgrade guide | Start here. The whole upgrade, step by step, on docs.ihasmail.org: what to fix first, rehearsing, running it, and what to check afterwards |
| 🧪 Rehearsing | Running preflight safely, what rehearse reports, rehearsing on a clone |
| 🐳 Docker | Container deployments: flags, what cutover carries and refuses, where the config goes |
| ⚠️ Known Stalwart problems | Tenants, the admin account, dropped ACME, certificates on mail ports, the extra recovery boot |
| 🛟 Recovery | Why recovery is your snapshot, what the tool keeps, never booting recovery mode again |
| 📊 Status | Command and package state, validation, field reports |
| ⚙️ Architecture | The design: phases, checkpoints, and the reasoning behind them |
| 🔧 Contributing | Building, testing, releases, and where the design is written down |
Requirements
- Linux (amd64 or arm64) with Stalwart 0.15.5, as a systemd service or a single Docker container
- root on the mail server
python3, for Stalwart's ownmigrate_v016.pystalwart-cli1.0.2 or later, a separate download from the server- an administrator account in Stalwart's directory — not the
fallback-adminfromconfig.toml, which does not survive the migration
Fix these on the server first
Both stop a migration, and preflight refuses on both:
- Tenants that share a domain. v0.16 requires an account in a tenant to
use only that tenant's domains.
stalwart-migrate tenantsshows who owns what. - A config-file admin. Migrate as a directory account with the admin role,
whose local part is unique and whose rights don't come only from
tenant-admin.
Details: Known Stalwart problems.
Install
ARCH=amd64 # or arm64
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/stalwart-migrate-linux-$ARCH.tar.gz
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS
tar -xzf stalwart-migrate-linux-$ARCH.tar.gz
sudo install -m 0755 stalwart-migrate /usr/local/bin/
stalwart-migrate version
Or build from source with Go 1.26.8 or newer:
go build -o stalwart-migrate ./cmd/stalwart-migrate.
Use
Give the admin password with --admin-password or
STALWART_MIGRATE_ADMIN_PASSWORD.
# 1. Read-only checks and a migration plan
sudo stalwart-migrate preflight --admin-url https://mail.example.com --admin-user [email protected]
# 2. Read-only: convert your settings and report what won't carry over
sudo stalwart-migrate rehearse --admin-url https://mail.example.com --admin-user [email protected]
# 3. Rehearse the real migration on a clone of the server (strongly recommended)
# 4. Migrate, once you have a snapshot you have checked you can restore
sudo stalwart-migrate run --admin-url https://mail.example.com --admin-user [email protected] \
--recovery-point-confirmed --yes
# Afterwards
sudo stalwart-migrate status <run-id> # which steps completed
sudo stalwart-migrate report <run-id> # what validation found
A Docker container also needs --container-path-unproven and --target-image;
see Docker deployments. After the migration, check the
certificate on ports 993 and 465, and recreate your ACME provider — Stalwart's
converter drops it without saying so.
Status
Every command works: preflight, rehearse, run, tenants, status and
report. It has migrated a production server (0.15.5 → 0.16.19, 8 seconds of
downtime) and, in another operator's hands, three more. Two things are still
open: some store migrations need one more recovery-mode boot, which is a manual
step, and the Docker path has never completed a migration against a real
Stalwart image. Details and field reports: Status.
License
Copyright (C) 2026 Coffey Labs. GPL-3.0-or-later: free software, with no
warranty. The full text is in LICENSE.
No third-party code is vendored — the tool is standard library only, and the
migrate_v016.py it downloads at runtime is Stalwart's own script, fetched
rather than redistributed.