Leaving Gmail with imapsync
I made the move off of using Gmail as my primary account earlier this year, leaving the existing account behind and moving to a new mailbox running on mailcow. At the time of making the switch, I had configured my old mailbox to forward emails to the new one, and had left the existing decade+ of emails to be moved at a later date.
Even a few years ago, this would have been totally fine, but with the uptick in SPF, DKIM, and DMARC (yay!) meant that for an increasing number of emails, they'd end up being rejected my my new mailbox for not meeting the authentication requirements. I probably could have added a rule/changed some settings in mailcow to ignore this for forwarded emails from my previous address, but since I also needed to migrate emails out of Gmail, I went a different route.
Enter imapsync
imapsync is a wonderful tool for handling most of the work that is mailbox syncing/migration- it's also been around since 2001 and has a ton of great documentation. I'd previously leveraged it for a migration between two hosting providers for a separate project, and it made the process fairly straightforward. It has provider-specific documentation for Gmail that helped me figure out a lot of the details below, which is definitely also worth reading through if you're considering doing a similar sync.
In order to keep it able to working with Google's IMAP after they disable app fully in January 2025, I set up oauth2_imap to use OAuth for authentication, and configured the sync job with these settings- the first one could be ommitted if using imapsync directly, and not via the mailcow UI:
Max. bytes per second | 300000 |
---|---|
Exclude objects (regex) | |
Custom parameters | --gmail1 --oauthaccesstoken1=/etc/dovecot/oauth2_imap/tokens/oauth2_tokens_example@example.com.txt |
Delete duplicates on destination (--delete2duplicates) | |
Delete from source when completed (--delete1) | |
Delete messages on destination that are not on source (--delete2) | |
Try to automap folders ("Sent items", "Sent" => "Sent" etc.) (--automap) | |
Skip duplicate messages across folders (first come, first serve) (--skipcrossduplicates) | |
Subscribe all folders (--subscribeall) |
Gmail IMAP Pitfalls
When linking via OAuth, I didn't have IMAP enabled in the Gmail mailbox settings to begin with, which is an important step for a sync like this. imapsync seemed to be able to connect without it on, but you can't get to the rest of the settings related to delete behavior.
For my use case, most of my emails weren't labeled (and I was fine with removing the labels from my account before starting, which I thought might save some trouble down the road- this was partially correct), and were just in the "All Mail" folder. I also didn't care about syncing the automatically-classified "Important" label over, so I initially added \[Gmail\]\/Important
to the Exclude objects (regex) setting, figuring this would be fine.
When a message is marked as deleted and expunged from the last visible IMAP folder...It turns out it was not fine.
By ignoring it by via a regex this way, representations of a message would not be deleted in the Important folder, so Google wouldn't ever actually delete it, and would leave it in the All Mail folder, rather than deleting the message. The key ended up being unchecking the Show in IMAP checkbox for the labels/categories that you don't want to sync in the label settings (in the left sidebar, More > Manage labels). Disabling Important and Chats got things showing up in the right way. Additionally, here's the Gmail IMAP settings I found worked for me (but might not have all been necessary):
Status | Enable IMAP |
---|---|
When I mark a message in IMAP as deleted | Auto-Expunge off - Wait for the client to update the server. |
When a message is marked as deleted and expunged from the last visible IMAP folder | Immediately delete the message forever |
Folder size limits | Do not limit the number of messages in an IMAP folder (default) |