I recently blogged about my e-mail backup strategy, using imap-backup. However I’ve found in real life this tool trips up quite a lot, and doesn’t really sync very satisfactorily on an ongoing basis.

It’s also been slightly nagging at me that I’m just mirroring – so if an e-mail is deleted it’s gone for good once the next backup has run. But equally I didn’t want to have to backup the whole shebang every time.

So, the solution I’ve reached is a combination of offlineimap and my old friend restic to do incremental backups securely and efficiently. The restic repository also goes up to good old sync.com, which is potentially a bit of duplication – but I wanted the snapshot capability (rather than individual file histories).

Basic workflow is

  1. Use offlineimap to mirror the imap server into local maildir folders
  2. Use restic to take a snapshot of the maildir folders

Of course, this is all done using Docker containers. So step one uses a volume within the docker container, step 2 uses a local filesystem mount for the restic repository.

Dockerfile

Pretty standard – install offlineimap and restic, then copy across the config file to ~/.offlineimaprc. Worth saying that ~/.offlineimaprc has passwords in it, so this image shouldn’t be published anywhere!

FROM ubuntu:latest AS build ENV HOME /root SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get -y --no-install-recommends install \ offlineimap ca-certificates restic RUN update-ca-certificates RUN adduser --system --uid 1000 --home /home/imapuser --shell /bin/bash imapuser RUN mkdir /imap-backup && chown imapuser /imap-backup USER imapuser ENV HOME /home/imapuser WORKDIR /home/imapuser COPY --chown=imapuser offlineimap.conf .offlineimaprc RUN chmod 0600 .offlineimaprc CMD ["offlineimap"]

.offlineimaprc

Best off reading the docs, but this is what I had to do to get it working nicely. Looking at it again now, the windows stuff and nametrans may no longer be required, as I originally used a mounted windows volume rather than a native docker volume for this step.

Of course you can add as many accounts as you want.

[general] accounts = account1,account2 metadata = /imap-backup/meta ui = basic fsync = False [DEFAULT] remotehost = imap.somewhere.com starttls = yes ssl = yes sslcacertfile = OS-DEFAULT expunge = no maildir-windows-compatible = yes nametrans = lambda folder: re.sub('[ \':/]', '_', folder) [Account account1] localrepository = Account1Local remoterepository = Account1Remote [Repository Account1Local] type = Maildir localfolders = /imap-backup/account1_eutony_net [Repository Account1Remote] type = IMAP readonly = True remoteuser = [email protected] remotepass = account1password [Account account2] localrepository = Account2Local remoterepository = Account2Remote [Repository Account2Local] type = Maildir localfolders = /imap-backup/account2_eutony_net [Repository Account2Remote] type = IMAP readonly = True remoteuser = [email protected] remotepass = account2password

Backup.bat

This is slightly more involved. I’ve got the best outcome when I sync each account individually. This script spins up a docker container for each account in turn, with a named volume mount called offlineimap which stores the local maildir (so it persists between runs). Then it spins up a final docker container to do the restic business.

That last line is a bit more involved it has to mount two volumes – the offlineimap as above, but then a Windows folder on my host PC mounted at /restic which stores the restic repository. This Windows folder is one managed by sync.com. As you can see, I needed to add some extra bits and pieces to the restic command to get it to do the incremental backup properly with a mounted windows volume.

docker container run --rm -v "offlineimap:/imap-backup" ^ offlineimap offlineimap -q -a account1 docker container run --rm -v "offlineimap:/imap-backup" ^ offlineimap offlineimap -q -a account2 docker container run -e RESTIC_PASSWORD=****** --rm -v "offlineimap:/imap-backup" ^ -v "C:/Users/james/Backups/email/offlineimap/restic:/restic" ^ offlineimap restic --ignore-inode --cache-dir=/imap-backup/.cache ^ --host=offlineimap -r /restic backup /imap-backup

In action

The first run is slow, as it has to download entire mailboxes, but thereafter it’s only a few seconds per account.

The abridged log output is shown below. The numbers won’t add up as this is from one of my actual runs, which syncs 6 or 7 e-mail accounts.

OfflineIMAP 8.0.0 Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception) imaplib2 v3.05, Python v3.10.6, OpenSSL 3.0.2 15 Mar 2022 *** Processing account account1 Establishing connection to imap.somewhere.com:993 (Account1Remote) Syncing Drafts: IMAP -> Maildir Skipping Drafts (not changed) Syncing Sent Items: IMAP -> Maildir Skipping Sent Items (not changed) Syncing Spam: IMAP -> Maildir Skipping Spam (not changed) Syncing INBOX: IMAP -> Maildir Copy message UID xxxx (1/2) Account1Remote:INBOX ->Account1Local:INBOX Copy message UID xxxx (2/2) Account1Remote:INBOX ->Account1Local:INBOX *** Finished account 'account1' in 0:06 OfflineIMAP 8.0.0 Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception) imaplib2 v3.05, Python v3.10.6, OpenSSL 3.0.2 15 Mar 2022 *** Processing account account2 [...] *** Finished account 'account1' in 0:01 using parent snapshot 25d4e6dd Files: 68 new, 9 changed, 39759 unmodified Dirs: 0 new, 49 changed, 1464 unmodified Added to the repo: 5.871 MiB processed 39836 files, 3.893 GiB in 0:02 snapshot ebd8f443 saved