Skip to Content

Email Migration

Migrating ~68 GB of Company Email to Google Workspace with imapsync and Docker
25 September 2026 by

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

  • --gmail2 applies Gmail-specific defaults.
  • --useheader Message-Id with --addheader makes every run idempotent: messages already at the destination are skipped, so a failed or interrupted run can simply be restarted.
  • --errorsmax is 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 nested INBOX/… 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.