I’ve been using a personal email server called Helm for the past few years to run my mail, and I’ve loved it. It did come with an annual fee of $99, which was used to pay for the “Gateway” server that they ran in AWS, along with support costs etc. But the mail lived in my apartment had a public IP address that trafficked all of my connections and all of my email. I liked the idea of having my mail in my apartment, and Helm made it really easy to do so.

Unfortunately, Helm is shutting down in a few days, and I have to migrate all of my mail off of it. Despite running the server in my apartment for the last several years, it wasn’t really my server, in that I don’t have a shell on it. I’m sure I could have started working to figure out how to pull that off when I got the shut down notice, but instead I said “Eh I’ll have plenty of time.” And here we are, shutdown is in 4 days.

Instead of trying to break into my own little triangle server of email, I decided I’d use this as an opportunity to run a mail-in-a-box server and try to migrate my mail to it. Mail-in-a-box has been around for some time and claims to be very easy to setup, albeit not supporting many configuration options. But having manually run a mail server before, I’ll take easy over highly customizable any day. I’m quite familiar with setting up SPF, DKIM, DMARC, the importance of a PTR record, etc. Hell, I’m even familiar with setting up MTA-STS. But it’s a chore, and easy sounds nice.

Unfortunately, since I don’t have a shell on my Helm server, I can’t just use doveadm to sync the mailbox to a new server and keep it synced while transferring DNS (both helm and mail-in-a-box insist on managing DNS themselves, which does make management easier but also makes migration kind of annoying). Instead, I’ll have to sync my mailboxes one at a time.

Setting up Mail-in-a-box

This process really was as easy as it says on the tin. I was one curl | sudo bash away from a functional mail server. The experience was a little clunky at first, since it didn’t tell me to setup DNS during the installation (I couldn’t have, anyways, at least not yet), so I had to access via the IP address directly. A few sudo failed to resolve hostname errors later, and I was in the mail-in-a-box web admin panel.

I added my mailboxes as new users in mail-in-a-box, and then, without logging into those mailboxes, I took to my terminal to get doveadm prepared.

Migrating over IMAP with doveadm

The documentation provided by dovecot is a little iffy on this topic. It’s technically all there, and technically all correct. But if you’ve never done it before, then it seems to make a few assumptions that I struggled to understand at first. The documentation gives some config settings to set, but doesn’t really tell you where to set them. After some googling and reviewing the config file of the version of dovecot I was running, I decided I should put the config in /etc/dovecot/conf.d/dovecot-migration.conf. After putting the file in the right place and setting my client configuration up to talk to the Helm server, I was ready to systemctl reload dovecot and get ready to run the command.

root@box:/etc/dovecot# cat conf.d/dovecot-migration.conf
# these are supported by standard adhering servers
imapc_features = rfc822.size fetch-headers

# If the old IMAP server uses INBOX. namespace prefix, set:
#imapc_list_prefix = INBOX

# Remote hostname
imapc_host = helm.<mydomain>.org

## if you are using TLS
imapc_ssl = imaps
imapc_port = 993

mail_prefetch_count = 20
#imapc_ssl_verify = no
## these default to system
ssl_client_ca_dir = /etc/ssl/certs
# or
#ssl_client_ca_file = /etc/ssl/certs/ca-certificates.crt

imapc_user = %u
imapc_password = <my_old_password_from_the_helm>

Provided the configuration is correct and dovecot loaded it, all that’s left is to backup the mail to my new server.

doveadm -o mail_fsync=never backup -R -u dade@<mydomain>.org imapc:

After this command completes, I can head over to the roundcube app provided by mail-in-a-box and make sure that my emails are all present. I repeated this step for each mailbox I needed to migrate, by just changing the username and password as necessary.

Finishing setup for mail-in-a-box

After migrating my mail, it was just about time to cut over my DNS. Technically a few emails could come in between when I did the backup and when I cut over DNS, but we’ll worry about that later.

The mail-in-a-box admin panel has a very handy status page that lists out the things that still need to be configured. So first, I’ll head over to Gandi and set the glue records1 for ns1.box.<mydomain>.org and ns2.box.<mydomain>.org, and then update my nameservers to no longer point at AWS and instead point at my glue records. The glue records point to my mail-in-a-box server.

After updating DNS, I sat back for a little while, chatting with a friend about IP based mail reputation and getting our own ASNs, until DNS did its thing and then mail-in-a-box was able to resolve all the DNS names correctly. Once mail-in-a-box became the nameserver, it also became the mail server. All new mail should be going to the new mail server and not to my helm any longer. We can test this by sending a quick email and making sure we receive it via roundcube on the mail-in-a-box server.

Retrieving the automatic backups

I try to keep most of my servers fairly locked down, which includes setting up administrative services (such as ssh) to only accept connections from a bastion server. I also don’t like services on my home network to be listening on the internet (e.g. I don’t want to port forward for my NAS). But I need to get the automatic backups that are being created by the mail-in-a-box provided duplicity service, and I need to be able to do it on a regular schedule, so that even if the server goes down, I can recover my mail. Mail-in-a-box supports many backup destinations, but I don’t really want to pay for additional commercial services, especially when I have tens of terabytes of free space at home.

Enter Tailscale. I’ve been using tailscale for my personal devices for several months now and have been pleased with how easy it makes it to connect my devices. Installing tailscale and getting the server added to my tailnet was remarkably easy, following the instructions for setting up tailscale on ubuntu 20.04, then I added another ufw firewall rule for allowing incoming traffic from my tailnet, via sudo ufw allow in on tailscale0. Now I can reach the server over the internet via my bastion, or via my tailnet.

My Synology DiskStation is also running tailscale, so now I can setup a scheduled task on my Synology to automatically rsync from the server to a local shared folder. I chose to go this direction because it was going to be easier to setup than having the mail-in-a-box rsync to the Synology, since the Synology doesn’t seem to support setting an SSH key for rsync users, and also rsync seems to require that users be Synology administrators to use it?? Absolutely trash tier design, so instead we’ll make the synology pull the encrypted backups from the remote service daily.

Another important thing, before going any further, is to save the secret key used by mail-in-a-box (i.e. duplicity) to a safe place, since I’ll need that to decrypt the backups in the future. I saved mine to my password manager vault.

I haven’t finished setting up automatic backups to my personal NAS yet, but I’ll update this page if/when I do. It should be pretty simple, just need to actually spend the time working through it.


  1. Glue records are a way to associate a hostname with an IP address at the registry level. This is necessary if your domain is going to be hosting it’s own nameservers, since otherwise resolvers would get stuck in a loop. ↩︎