Client: International Salon Supplies
Role: Engineer (planning & execution)
Period: Jan 2026 – Feb 2026
Status: Completed
Tech stack: imapsync, Docker Compose, IMAP, Google Workspace, Axigen
A zero-loss, re-runnable migration of the company's mailboxes from a self-hosted Axigen mail server to Google Workspace: dry-run audits, Message-ID-based deduplication, Gmail-aware folder mapping, a fix for malformed messages, and an archive pattern for departing staff. About 106,000 messages and 68 GB moved.
The goal
Move every company mailbox from a self-hosted Axigen server to Google Workspace with no lost mail, no duplicates and minimal disruption. The largest mailbox alone held ~69,500 messages (~48.6 GB).
Tooling
I used imapsync (the official Docker image) driven by Docker Compose files, one per migration wave. Credentials were supplied through environment variables and never written into the files. Logs and cache directories were mounted to persist between runs.
Approach
1. Audit first
Every mailbox got a dry run (--dry) before any mail moved. The audit showed folder structures, sizes and problem messages up front.
2. Make it safe to re-run
--gmail2applies Gmail-specific defaults.--useheader Message-Idwith--addheadermakes every run idempotent: messages already at the destination are skipped, so a failed or interrupted run can simply be restarted.--errorsmaxis set high so one corrupt message doesn't abort a 60,000-message mailbox.
3. Fix what's broken on the source
Some messages on the old server ended with trailing NUL bytes, which Gmail rejects. A --regexmess rule strips them in transit.
4. Map folders to labels deliberately
- Trash, Spam, Junk, Deleted Items and Gmail's "All Mail" are excluded.
- Explicit folder mappings (e.g.
Archive→_Archive), with nestedINBOX/…folders flattened to top-level labels where that suited the user.
5. Archive departing staff mailboxes
For former staff, --regextrans2 places the entire source mailbox under _Archive/<person>/ inside a shared destination mailbox. The history stays searchable in one shared place.
6. Verify
Final passes compare message counts on both sides and transfer only the difference. One verification run skipped 64,639 messages already present and moved the 54 that had arrived since.
Results
- ~106,000 messages / ~68 GB migrated across role and department mailboxes
- Final passes reporting all folders synced with 0 errors. Runs that hit errors were simply re-run, thanks to the idempotent setup.
- 21 run logs kept as an audit trail
Takeaway
Mail migrations go wrong when they're treated as a one-shot copy. Making every run safe to repeat turned the migration into a routine: run, check, re-run.