After running my own email server on a VPS for around two decades – I finally decided to shut it down and pay for professional email hosting. Email migration is tricky to get right, so I thought I’d take a few notes.
To be clear, hosting my own email was always a terrible idea (maintaining a server, dealing with spammers, chasing down why Microsoft has randomly blocked my email server for no reason again, etc.) .. but it was cheap – and fun, in a nerdy kind of way.
No regrets in having done so, but I’m glad I don’t have to think about it any more!
Choosing a provider
Wishlist
In researching options for a new provider, I had a few fairly tall orders:
- Multiple domains with multiple mailboxes per domain
- Per-user email filtering and routing (Sieve support)
- A large number of aliases of different types (forwarding to single recipients, forwarding to multiple recipients, subaddressing aliases, external forwarding)
- An API, for alias creation if nothing else
- Sensible antispam functionality
- Privacy and security
..and it had to be reasonably priced.
Pricing immediately ruled out many providers for me. Per-mailbox pricing makes a lot of sense for a provider, but for me it would have meant paying several hundred dollars per year.
Providers
As of 2026, there are a small number of providers that offer per-account pricing instead. Effectively unlimited domains and mailboxes; you instead pay for usage (typically, by storage volume). Three providers made my shortlist: Purelymail, Migadu and MXRoute. These providers have all been around for several years, and all of them have generally good reviews. So which one to pick?
Here’s the summary version of my research:
| Requirements | Purelymail | Migadu | MXRoute |
|---|---|---|---|
| Domains/mailboxes | Unlimited | Unlimited | Unlimited |
| Filtering/routing | Great | Great | Great |
| Aliases | Unlimited | Unlimited | Unlimited |
| Forwarding | Good (but no ARC support) | Not great (can’t have pure external forwarders) | Great (everything supported) |
| API | Great | Good (no easy Bitwarden integration) | Great |
| Antispam | Good | Good | Good |
| Privacy | Great (encryption at rest) | Great (European hosting) | Good |
| Security | Great (2FA + passkey support for web logins, app specific passwords) | Good | Good |
| Pricing | Great | Good | Good |
Purelymail’s pricing was very hard to ignore: at time of writing, US$10 per year for the whole account! They’ve already flagged that the price may rise, but even if it goes up 4x – it’ll still be a good deal.
Purelymail has a few things I don’t like – no configurable spam filters, no access to logs, no support for ARC. Seeing as everything else seemed decent, I picked Purelymail.
Forwarding without ARC
Given the lack of ARC support, I wanted to understand how external forwarding would work. Unreliable forwarding would be a dealbreaker for me!
After migrating in a lower-priority domain to test email forwarding, here’s what I found: Purelymail authenticates the inbound message using all the usual suspects (SPF, DKIM, and DMARC), preserves the DKIM signature by not touching the DKIM-signed content, then rewrites the SMTP envelope sender instead of using SRS.
The result of this is that SPF passes (but without domain alignment), and DKIM passes because the signature can still be verified. Because of the DKIM pass, DMARC also passes and thus forwarding doesn’t break.
Tl;dr: setting up all DNS record types for mail and mail authentication (MX, SPF, DKIM, DMARC) when using Purelymail for mail hosting is a non-negotiable.
Migration using Imapsync
Purelymail do offer a mail migration tool, but I wanted to set things up myself. I used Imapsync, as it’s considered the gold standard for IMAP mailbox migrations. Here are some notes:
Docker configuration
Here’s the Docker Compose config – note that this allows you to run imapsync from the command line. This means you can migrate different mailboxes with different commands, rather than adjusting the Docker configuration each time:
services:
imapsync:
image: gilleslamiral/imapsync:latest
container_name: imapsync
command: ["sleep", "infinity"]
volumes:
- log:/var/tmp/log
- data:/var/tmp/data
restart: unless-stopped
volumes:
log:
data:The data volume lets me drop user-pass1.txt and user-pass2.txt into a folder on the local machine, and have it readable by the Docker image. While this means that the email passwords are sitting in the clear in temporary text files – they aren’t sitting in the CLI history!
If you want the log files, remember to update permissions on the local folder such that Docker can write to it. The location of your folder may be different to mine:
sudo chmod 777 /var/lib/docker/volumes/imapsync_log/_dataMigration process
Several days* before the cutover
Configure the domain in Purelymail with the following:
- DNS records: only configure the proof-of-ownership DNS record – do not adjust any of the existing DNS records for email yet. This lets you migrate the mailboxes ahead of the cutover.
- Deliver Mail To: set to External server (MX). This ensures email routed by Purelymail from other domains doesn’t vanish.
The first round of Imapsync commands migrate most of the email ahead of the cutover. This will need the authentication details for each mailbox, and the commands I’m using involve putting the password for each mailbox into a file. Not particularly scalable, but I didn’t need scale.
The amount of time this takes dictates how long before the cutover you need to be doing this – plan accordingly.
I also raised a support request with Purelymail to confirm the rate limiting settings to use, as I didn’t want the email migration to break part way through because I was trying to go too fast. Larger mailboxes will take several hours (or even days), but better that than getting blocked!
By way of example: a 7GB mailbox took ~8 hours to migrate, and it completed in a single pass without stopping.
There are also other considerations – for example, are the folder structures the same, or do they need to be rewritten? Are the default mailboxes (INBOX, Sent, etc.) all correct? Coming from Dovecot IMAP, everything was already in the right shape – but it’s worth taking the time to do a dry run and check these things:
sudo docker exec -it imapsync imapsync --host1 {original mail server} --user1 {email address} --passfile1 /var/tmp/data/user-pass1.txt --host2 imap.purelymail.com --user2 {email address} --passfile2 /var/tmp/data/user-pass2.txt --dry --justfolders --log --logdir /var/tmp/logOnce you’ve confirmed you can connect and the folder structure looks OK, you’re ready to begin the migration. If there’s anything out of alignment, adjust the command and keep doing dry runs until it’s looking good.
The proof is in the actual migrated mailbox, so I started with lower priority mailbox to really check before I migrated the important mailboxes. Here’s the command for the migration:
sudo docker exec -it imapsync imapsync --host1 {original mail server} --user1 {email address} --passfile1 /var/tmp/data/user-pass1.txt --host2 imap.purelymail.com --user2 {email address} --passfile2 /var/tmp/data/user-pass2.txt --usecache --delete2 --log --logdir /var/tmp/log --maxbytespersecond 500000 --maxmessagespersecond 5A day* before the cutover
Drop the TTL to 300 on the DNS records for mail: MX, SPF, DKIM, DMARC
The timeframe needed here entirely depends on the TTL for the existing records. If you have your MX record set with a TTL of 604800 seconds – you need to do this step a week before the cutover.
You can also run the above imapsync command for each of your mailboxes multiple times during this process to make sure there’s very little left to migrate during cutover.
| Final check: You should be able to log into the Purelymail webmail at this point, and all of your email should be displayed correctly. Pay attention to the folder structure (are nested folders displayed correctly?), and pay special attention to these folders: INBOX, Drafts, Sent, Spam, Deleted Items. You want to find any problems before you commit to the cutover..! |
During the cutover
Run things in this order:
- Update the MX, SPF, DKIM and DMARC records to suit Purelymail. A few notes:
- There are no secondary MX records – nor do you need them. Make sure you remove any that you have; the only MX record you should have is the Purelymail record.
- Add in the Purelymail SPF and DKIM records, taking care to remove any corresponding SPF and DKIM records for your old email server.
- You don’t have to use the Purelymail DMARC record – but you definitely need some sort of DMARC record. If you decide to do your own thing, know that the DMARC report emails you’ll recieve can be fairly cryptic; I quite like Valimail for DMARC monitoring instead.
- If you also use services like Mailchimp, Sendgrid, Salesforce etc. to send email:
- Don’t use the SPF record that Purelymail gives you. Instead, add
include:_spf.purelymail.comto your existing SPF record. - You probably also have DKIM records for these services (look for DNS records that mention “domainkey”). Leave those in place.
- Don’t use the SPF record that Purelymail gives you. Instead, add
- Wait ~5 minutes for the DNS record updates to start propagating – you can check for DNS propagation using DNS Checker. Check both MX and TXT records to ensure you see the Purelymail mail server and the SPF include line above.
- Once the DNS records have updated, try sending a few test emails from Gmail, Outlook etc. to your inbox. Use the Purelymail webmail to receive and respond to them – make sure the reply emails make it back to Gmail or Outlook!
Now that everything is working, it’s time to start configuring email programs to use the new login details. Make sure you configure the autoconfig and autodiscover DNS records to make your life easier.
One day after the cutover
If everything is working at this point, it’s safe to update the TTL on the DNS records back to whatever you were previously using – leaving them at 300 generates unnecessary DNS traffic.
It’s also a good idea to do a final sync to make sure any email delivered to the old mail server gets added to the new mail server.
Note that this is a different command to the one above – the previous imapsync command will delete any email on the new server that’s not on the old server!
sudo docker exec -it imapsync imapsync --host1 {original mail server} --user1 {email address} --passfile1 /var/tmp/data/user-pass1.txt --host2 imap.purelymail.com --user2 {email address} --passfile2 /var/tmp/data/user-pass2.txt --usecache --log --logdir /var/tmp/log --maxbytespersecond 500000 --maxmessagespersecond 5Aliases and APIs
Migrating aliases
One of the challenges I faced was migrating several hundred aliases. Thankfully, Purelymail has a relatively straightforward API endpoint for alias creation:
POST to https://purelymail.com/api/v0/createRoutingRule
Headers:
Purelymail-Api-Token: {API Key}
Body:
{
"domainName": "{domain part of the alias email address}",
"prefix": false,
"matchUser": "{user part of the alias email address}",
"targetAddresses": [
"{full destination email address}"
],
"catchall": false
}The prefix is false if the routing rule has to match the email address exactly, or true if the email address can have other characters after the user (e.g., for subaddressing).
From there, I extracted the list of aliases from the existing mail server – reformatted them into a CSV file with three columns (alias, domain, destination), and then set up a Postman run to go through the list. All aliases added in under 15 minutes, including the time needed to set up a Postman account!
Bitwarden aliases
If you use the Bitwarden password manager and are in a position to run a Docker container with some sort of reverse proxy in front of it, you can run the Bitwarden Alias Provider container to allow Bitwarden to automatically generate email aliases for each account you set up. This ensures each account you have has a unique email address.
I’ve tested it and it works well enough, but I ultimately decided it was not for me. I do have unique aliases for most accounts with companies, but aliases that don’t tell me where the email address is used aren’t helpful to me personally.
Filters and Routing
If you have a Managesieve service running, you will also need to export all of the rules and re-import them to Purelymail. I wasn’t previously using Sieve mail routing scripts, so I didn’t have anything to migrate.
