Files

Mailbox migration: docker-mailserver → Stalwart

Tools to move accounts, aliases, and mail from a docker-mailserver instance into this Stalwart deployment.

Overview

Step Linux (bash) Windows (PowerShell) Purpose
1 export-docker-mailserver.sh (run on the Linux docker-mailserver host) Dumps accounts + aliases from docker-mailserver's config files into accounts.csv
2 (manual) (manual) Fill in new_password (and old_password if not already known) in accounts.csv
3 generate-account-plan.sh New-AccountPlan.ps1 Turns accounts.csv into a stalwart-cli apply NDJSON plan that creates the domain, accounts, and aliases in Stalwart
4 stalwart-cli apply stalwart-cli apply Applies the generated plan to Stalwart
5 sync-mailboxes.sh Sync-Mailboxes.ps1 Runs imapsync (via Docker) for every account, copying all folders/messages from docker-mailserver to Stalwart
6 (manual) (manual) Verify, then repoint MX/DNS and decommission docker-mailserver

Both old and new mail servers run Linux, so the bash scripts are the primary path; the PowerShell scripts are equivalent for driving the migration from a Windows workstation. Make the bash scripts executable once: chmod +x migration/*.sh. generate-account-plan.sh requires jq (apt install jq / dnf install jq).

docker-mailserver stores passwords as one-way hashes (config/postfix-accounts.cf), so they cannot be reused directly in Stalwart. Each account gets a new password during migration; old_password is only needed temporarily so imapsync can log in to the source server to copy mail.

1. Export accounts and aliases from docker-mailserver

Run on the docker-mailserver host (or docker exec into its container), pointing at its config directory:

./export-docker-mailserver.sh /path/to/docker-mailserver/config > accounts.csv

This produces accounts.csv with columns email,new_password,old_password,aliases,quota_mb, new_password and old_password left blank for you to fill in.

2. Fill in passwords

Edit accounts.csv:

  • new_password: the password the account will have on Stalwart. Generate strong random values; users can change them later via the WebUI.
  • old_password: the account's current, plaintext docker-mailserver password, needed only so imapsync can authenticate to the source during copy. If you don't have it, reset it temporarily on docker-mailserver with setup email update <email> <temp-password> and record that value here.
  • aliases: optional, ;-separated list of extra local-parts on the same domain that should deliver to this mailbox (e.g. info;sales).
  • quota_mb: optional mailbox quota in MB (blank = unlimited).

Never commit a filled-in accounts.csv; it contains plaintext passwords.

3. Generate the Stalwart account plan

Linux:

./generate-account-plan.sh -c accounts.csv -d eternityproject.fi -o accounts-plan.ndjson

Windows:

./New-AccountPlan.ps1 -CsvPath accounts.csv -Domain eternityproject.fi -OutFile accounts-plan.ndjson

This upserts the Domain, one Account/User per row (with a Password credential set to new_password and any aliases), so re-running after edits is safe.

4. Apply the plan to Stalwart

Linux:

export STALWART_URL="http://127.0.0.1:8081"
export STALWART_USER="admin"
export STALWART_PASSWORD="<admin password>"

stalwart-cli apply --file accounts-plan.ndjson --dry-run
stalwart-cli apply --file accounts-plan.ndjson

Windows:

$env:STALWART_URL = "http://localhost:8081"
$env:STALWART_USER = "admin"
$env:STALWART_PASSWORD = "<admin password>"

stalwart-cli apply --file accounts-plan.ndjson --dry-run
stalwart-cli apply --file accounts-plan.ndjson

Use the permanent administrator username and password created in the Stalwart setup UI, not a mailbox login. The default Compose deployment binds the administration API only to the local host, so https://mail.eternityproject.fi is not the CLI endpoint unless you have separately configured a reverse proxy for it. Confirm the CLI can reach and authenticate to the local API before applying a real plan:

stalwart-cli apply --url http://localhost:8081 --user admin --file accounts-plan.ndjson --dry-run

The command prompts for the password when STALWART_PASSWORD and --password are omitted. If the command says authentication failed, reset or use the correct permanent administrator password in the Stalwart WebUI. If it says failed to connect, verify docker compose ps stalwart and use http://localhost:8081 on Docker Desktop or http://127.0.0.1:8081 directly on the Linux server.

5. Copy mail with imapsync

The sync scripts use the upstream gilleslamiral/imapsync Docker image. The first run pulls the image automatically, so Docker Hub access is required.

Linux:

./sync-mailboxes.sh -c accounts.csv -s mail.old-domain.example -t mail.eternityproject.fi -n

Windows:

./Sync-Mailboxes.ps1 `
  -CsvPath accounts.csv `
  -SourceHost mail.old-domain.example `
  -DestHost mail.eternityproject.fi `
  -DryRun

(-n / -DryRun runs imapsync in dry-run mode first; drop it to actually copy messages.)

Review the dry-run output, then re-run without -DryRun to actually copy messages. The script is safe to re-run: imapsync skips messages that already exist at the destination.

6. Cut over

  • Spot-check folder counts/message counts on a few mailboxes in the Stalwart WebUI.
  • Run Sync-Mailboxes.ps1 once more shortly before cutover to catch mail received during the migration window.
  • Update MX records to point at mail.eternityproject.fi (see the top-level README.md).
  • Once confirmed, decommission docker-mailserver and rotate the temporary old_password values you set in step 2.