We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.Privacy Policy

EvilMailEvilMail
FeaturesEverything EvilMail can doTemp Mail for…Temp mail for Discord, ChatGPT & moreNetwork ToolsDNS, WHOIS, port & network toolsCompareEvilMail vs other temp mail services
APIREST API reference & guidesTemp Mail APICopy-paste code in every language
AppsPricingBlog
About UsWho we are & what we buildTrust & SecurityHow we protect your dataFAQAnswers to common questionsContactGet in touch with the team
LoginRegister
EvilMailEvilMail
AppsPricingBlog
LoginRegister
HomeBlogimapsync at Scale: Folder Mapping, Quota, and Rate-Limit Gotchas for 500-Mailbox Migrations
Tutorial

imapsync at Scale: Folder Mapping, Quota, and Rate-Limit Gotchas for 500-Mailbox Migrations

A war-story runbook for migrating hundreds of mailboxes with imapsync. The copy is the easy part — the real work is folder mapping, quota headroom, and staying under Gmail and Microsoft 365 throttles. Two passes, capped parallelism, and a repeatable per-mailbox invocation.

EvilMail Team
July 29, 2026
13 min read
imapsync at Scale: Folder Mapping, Quota, and Rate-Limit Gotchas for 500-Mailbox Migrations
Contents
Why "just run imapsync" breaks at 500 mailboxesThe reference invocation and what every flag is doingFolder mapping: --automap, --regextrans2, --sep1, --subfolder2Quota: the sync fails silently when the destination fillsRate limits and throttling: the flags that keep you unbannedOrchestrating hundreds of mailboxesPre-cutover checklist

Why "just run imapsync" breaks at 500 mailboxes

The classic first attempt looks like this, and it dies around account 40:

bash
# The loop that will page you at 3am
for user in $(cat users.txt); do
  imapsync --host1 imap.gmail.com --user1 "$user" --password1 "$pw" \
           --host2 mail.evilmail.pro --user2 "$user" --password2 "$pw2"
done

By the fortieth mailbox, Gmail is answering [ALERT] Too many simultaneous connections on the source side, your destination Dovecot has thrown Quota exceeded (mailbox for user is full) on three accounts, and every Gmail user's mail history is duplicated because [Gmail]/All Mail copied every message a second time. None of this is random. imapsync at scale fails in exactly three structural ways, and all three are preventable:

  • Folder mapping — source and destination disagree about folder names and hierarchy separators, and Gmail's labels-as-folders model double-copies everything.
  • Quota — the destination fills mid-sync, and on-disk size on the target is bigger than the source reported.
  • Rate limits — the source throttles you by connection count, bytes per day, or sustained IMAP load.

The lesson: the copy itself is trivial. The work is the mapping, the throttling, and the reconciliation around it. So you never run one monolithic unsupervised loop. You run two passes — a discovery pass to learn the source's quirks, then an incremental pass at cutover that moves only the delta. That second pass is the migration your users actually feel. This is the procedure we use to onboard bulk domains onto Dovecot at evilmail.pro without tripping Gmail, Exchange, or cPanel source throttles.

Two-pass migration: dataflow + cutover timeline Source IMAP Gmail / M365 cPanel / Courier imapsync orchestrator throttle valve --maxbytespersecond · -j4 Dovecot evilmail.pro quota × 1.3 T-7d full pass T-1d lower DNS TTL T-0 MX flip T+0 incremental delta Pass 1 moves the bulk days ahead. Pass 2 at cutover moves only the delta — mail delivered during the DNS/MX window. That second pass is the real migration.

The reference invocation and what every flag is doing

Here is the canonical command — the one you tune once per source type, then fan out. Every flag earns its place at scale:

bash
imapsync \
  --host1 imap.gmail.com --ssl1 --user1 [email protected] --password1 "$SRC_PW" \
  --host2 mail.evilmail.pro --ssl2 --user2 [email protected] --password2 "$DST_PW" \
  --automap \
  --useuid \
  --syncinternaldates \
  --addheader \
  --maxsize 40_000_000 \
  --exclude '\[Gmail\]/All Mail' \
  --logfile /var/log/imapsync/alice.log \
  --tmpdir /var/tmp/imapsync

What matters and why:

  • `--automap` builds a best-guess mapping of the special folders (Sent, Drafts, Trash, Junk) across naming schemes. You still verify it — trust nothing here.
  • `--useuid` keys message identity on the IMAP UID rather than a header hash. Together with imapsync's per-user state cache, this is what makes pass two cheap: it re-transfers only messages it hasn't already confirmed on the destination.
  • `--syncinternaldates` preserves the server-side INTERNALDATE so migrated mail sorts by its real received time, not migration time. Skip it and every inbox looks like it all arrived today.
  • `--addheader` stamps a tracking header on each copied message — invaluable when you need to prove which run touched what.
  • `--maxsize` plus a --tmpdir on fast local disk stops one monster attachment from stalling a run and keeps temp churn off network storage.

Before any of this moves a byte, run the discovery pass:

bash
# Validate the folder tree, move nothing
imapsync ... --dry --justfolders
# Pre-flight sizes so you can set quota headroom
imapsync ... --justfoldersizes

--dry --justfolders shows exactly which folders imapsync intends to create on the destination and how it mapped them, without copying a single message. It is the cheapest insurance in the whole process.

Folder mapping: --automap, --regextrans2, --sep1, --subfolder2

This is the part that eats a day if you skip the dry run. --automap handles the common special folders, but it does not know that your legacy Courier server used a dot separator with an INBOX. prefix, and it will happily copy Gmail's [Gmail]/All Mail — which holds a copy of every message carrying any label — leaving a destination roughly twice the size of the source, duplicates everywhere.

The fixes, flag by flag:

  • Gmail All Mail / Important dedup. Exclude the label-as-folder pseudo-folders so each message copies exactly once from its real folder: --exclude '\[Gmail\]/All Mail' --exclude '\[Gmail\]/Important'.
  • Renaming folders. Rewrite the transfer path with a regex: --regextrans2 's,INBOX\.Sent,Sent,' turns INBOX.Sent into a clean Sent. Chain several --regextrans2 rules as needed.
  • Separator and prefix mismatches. Old Dovecot and Courier use . as the hierarchy separator with everything under INBOX.; Gmail and modern Dovecot use /. Describe the source layout with --sep1 . --prefix1 "INBOX." and imapsync re-roots the tree correctly on a /-separated destination.
  • Staged cutover. To land everything under a single parent so users can review before you promote it, add
Folder mapping: which flag fixes which problem SOURCE (Gmail / Courier) INBOX [Gmail]/Sent Mail INBOX.Drafts [Gmail]/All Mail [Gmail]/Trash DEST (Dovecot, "/" sep) INBOX Sent Drafts (dropped) Trash --automap --automap --sep1 . --prefix1 INBOX. --exclude '\[Gmail\]/All Mail' --regextrans2 Verify this exact mapping with --dry --justfolders on a sample account before any real copy. All Mail is excluded so each message copies once from its real folder — not once per label. --regextrans2 's,INBOX\.Sent,Sent,' collapses the legacy namespace --subfolder2 "Migrated" stages everything under one parent

Quota: the sync fails silently when the destination fills

The failure signature is Error: Store failed ... Quota exceeded (mailbox for user is full), buried mid-log, after which imapsync keeps trying every remaining message and failing each one. If you are not reading per-user logs, you find out when the user complains that half their mail is missing.

Two numbers drive this. First, on-disk size on the destination is larger than the source reported — IMAP re-encoding, per-message index overhead, and Dovecot's own metadata inflate it. The working rule: set destination quota to source size × 1.3 before you start. Pre-flight the source with --justfoldersizes, then provision the target quota with that headroom.

Check and reconcile Dovecot quota directly:

bash
# Before: confirm the destination quota and current usage
doveadm quota get -u [email protected]
# After the migration: force a recount so reported usage is accurate
doveadm quota recalc -u [email protected]

Bound what moves so a few giant mailboxes do not blow the budget:

  • `--maxsize 40_000_000` skips individual messages over ~40 MB — usually a handful of huge attachments — which you chase in a separate later pass.
  • `--maxage 365` (paired with --minage) migrates the last year first, then backfills older mail once the account is live.
  • When the source itself is over quota, set --maxsize aggressively to move the bulk, cut the user over, then run a tail pass with the limit lifted.

Rate limits and throttling: the flags that keep you unbanned

Source-side throttling is the real ceiling on migration speed, and it is provider-specific. Gmail enforces roughly a 2500 MB/day/user IMAP download cap plus per-IP simultaneous-connection limits; blow past them and you get [ALERT] Too many simultaneous connections or a temporary lockout. Microsoft 365 throttles sustained IMAP load and starts answering Server Unavailable once you lean on it. cPanel/Dovecot sources are gentler but still refuse connections past their per-user limit.

Two layers of control:

  • Per-process caps. --maxbytespersecond 1_500_000 and --maxmessagespersecond 10 keep one imapsync process politely under the source's radar. --noabletosearch skips server-side SEARCH when the source's implementation is broken (some Exchange front-ends).
  • Parallelism at the orchestration layer. This is the one people get wrong. You cap the number of *concurrent imapsync processes*, not the throughput of one. Four to eight parallel processes against Gmail is sane; hundreds is a self-inflicted denial of service.
bash
# CSV: host1;user1;password1;host2;user2;password2
cat accounts.csv | parallel -j4 --colsep ';' \
  imapsync --host1 {1} --ssl1 --user1 {2} --password1 {3} \
           --host2 {4} --ssl2 --user2 {5} --password2 {6} \
           --automap --useuid --syncinternaldates \
           --maxbytespersecond 1_500_000 \
           --logfile /var/log/imapsync/{2}.log

The 2026 reality: basic auth is gone. Microsoft 365 removed basic auth for IMAP, and Gmail requires app-passwords or OAuth. For OAuth sources you pass a token file instead of a password: --oauthaccesstoken1 /path/to/token. Otherwise issue per-user app-passwords ahead of time — that provisioning step, not the copy, is often the migration's critical path.

Orchestrating hundreds of mailboxes

The pattern that scales is a CSV fed to parallel (above), one log file per account, and a retry/quarantine list built from exit codes. imapsync returns non-zero on failure, and --errorsmax 50 makes a single account give up after 50 errors instead of hammering a full mailbox against a wall:

bash
# Retry/quarantine driver
while read -r line; do
  user=$(echo "$line" | cut -d';' -f2)
  imapsync ... --errorsmax 50 --logfile /var/log/imapsync/$user.log \
    && echo "$line" >> done.csv \
    || echo "$line" >> retry.csv
done < accounts.csv

After the first full pass, retry.csv is your short list. Re-run only that — thanks to --useuid and the persistent state cache, accounts that partially succeeded resume as near-zero-delta.

The cutover is the two-pass timeline from the first diagram. Run the full pass days ahead. Lower your DNS TTL 24–48 hours before the flip so the MX change propagates fast. Flip the MX. Then run the final incremental pass — the same command, now moving only mail delivered during the propagation window. That last pass, not the big one, is the migration your users feel.

Pre-cutover checklist

  • Verified --dry --justfolders tree on at least three sample accounts per source type.
  • Destination quota set to source size × 1.3; doveadm quota get confirms headroom.
  • App-passwords or OAuth tokens issued for every account; no basic-auth assumptions.
  • Parallelism capped (-j4) and load-tested against the real source without triggering throttle alerts.
  • Excludes confirmed for [Gmail]/All Mail, [Gmail]/Important, and oversize attachments.
  • Per-user logs under /var/log/imapsync/ and a working retry/quarantine loop keyed on exit code.
  • Incremental re-run tested end to end — the second pass reports near-zero moved messages.
  • DNS TTL lowered ahead of the MX flip; doveadm quota recalc scheduled post-migration.

Get these eight right and a 500-mailbox migration is boring — which is exactly what a migration should be.

Share
𝕏 TwitterLinkedInReddit
PreviousSplitting the Postfix Queue Across Per-Destination TransportsNextNever Test a Mail Change on Production — Replay It

Related Articles

Tutorial

Splitting the Postfix Queue Across Per-Destination Transports

12 min read
Tutorial

Running Two Postfix Instances on One Host: Inbound Filtering vs. Outbound Sending

11 min read
Tutorial

Load Balancing SMTP and IMAP Behind HAProxy Without Losing the Client IP

13 min read
EvilMailEvilMail

EvilMail — free temp mail and disposable email service. No registration required.

Product

FeaturesPricingAPITools

Resources

BlogFAQGlossaryRegisterAbout Us

Legal

Privacy PolicyTerms of ServiceTrust & SecurityContact
© 2026 EvilMail. All rights reserved.
--subfolder2 "Migrated"
.