Files
ep-stalwart-v2/migration

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="https://mail.eternityproject.fi"
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 = "https://mail.eternityproject.fi"
$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

5. Copy mail with imapsync

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.