Jérôme Belleman
Home  •  Tools  •  Posts  •  Talks  •  Travels  •  Graphics  •  About Me

Towards Automatically Backing Up Mails with Mutt

16 Dec 2017

Even though mutt is an interactive mailer, it comes with facilities to perform operations in batch. I've cooked up here an example to back up mailboxes.

1 The Tool for the Job?

The goal here is to automatically – typically in the background – perform backups of a mailbox that would be hosted in an IMAP server. Backups would have to be incremental, so as to not have to download the entire mailbox each time, which is instrumental to save space and time, especially if you commonly work with e.g. 100k-messages mailboxes, like I have the nasty habit to do.

It's questionable whether or not mutt should be used to automatically perform mailbox backups at all. Again, it remains an interactive client, and trying to run automated tasks feels like bending its purpose. However, when I went looking for mail synchronisation tools supporting IMAP, I had a hard time finding one that cut the mustard. They were either slow, or unable to authenticate the way I needed it, or didn't know how to check certificates, or wouldn't back up mailboxes incrementally.

I always regarded OfflineIMAP as the most able of all. That being said, the discussion their documentation offers when it comes to backing up mail, the risk of losing all your backed up mail while syncing if no mail is found on the server, and the necessity to come up with a back-end keeping a history of backups in case it happens put me well off. They were all valid points, certainly, there's no denying it. But they put me off nonetheless, and I decided to turn to a tool I felt more at home with: mutt. And who doesn't.

2 Configuring Mutt

There's a number of configuration variables that are worth setting up to begin with. Writing a bespoke muttrc file – call it e.g. .backup.muttrc – which you'll specifically use for the purpose of backups with mutt -F is probably advisable:

set header_cache=~/.muttcache
set spoolfile=imaps://fred@example.com/INBOX
set read_only
set auto_tag
set confirmcreate=no
set confirmappend=no

Using a header_cache will significantly speed up loading the mailbox, and why not rely on the one you normally keep for interactive use anyway? Choosing a spoolfile is necessary to automatically open the mailbox to back up. Opening the mailbox read_only is advisable to avert unexpected changes to the mailbox. The auto_tag variable is useful to have mutt assume operations are only carried out on tagged messages, if any. Setting confirmcreate=no and confirmappend=no causes mutt to not prompt whether to create a new mailbox file or to append to it: it will just do so without further ado.

3 Automating Mutt

Mutt comes with the push command which enables you to programmatically run functions and simulate sequences of keystrokes being typed. Think about how you'd carry out your backup interactively, and just type it as argument to the push command. The documentation advises to use full function names as opposed to key bindings, in case they have been altered.

Consider backing up your latest messages, which involves the following steps:

  1. Sorting the mailbox by date received (to also handle e.g. recently-received mails which mysteriously have their date set far in the past, as it happens once in a while).
  2. Tag those messages which haven't been backed up yet.
  3. Copy them to a local mailbox file.
  4. Quit.

Programming mutt to do so automatically can be done by adding a push command along those lines, e.g. just below the variables you previously set:

push '\
    <sort-mailbox>r\
    <tag-pattern>~m95000-<enter>\
    <copy-message>INBOX.backup<enter>\
    <quit>\
'

Note how these different operations were specified in a single push command. Doing so with a push command for each operation leads to unexpected behaviours, because they can be sort of executed simultaneously. And you'll soon be glad you'd set read_only.

4 Incremental Backups

The ~m95000- pattern tags the 95000th message and any subsequent one in the chosen order – date received, here. Assuming you never (re)move message from your INBOX – a plausible scenario if you use a solid spam filter –, this number will steadily increase. One way to have it increase automatically is by replacing it with a placeholder, $limit here:

push '\
    <sort-mailbox>r\
    <tag-pattern>~mLIMIT-<enter>\
    <copy-message>INBOX.backup<enter>\
    <quit>\
'

You could then have e.g. sed change it on the fly before running mutt:

mutt -F <(sed "s/LIMIT/95000/" .backup.muttrc)

To make things even more interesting, you can work out the value of LIMIT from your previous backup. Running this command does the business, as each message has got a single Message-ID field:

grep Message-ID INBOX.backup | wc -l

But this would cause the last backed up message to be included as the first one to back up this run. So we need to add 1 to this limit:

expr $(grep Message-ID INBOX.backup | wc -l) + 1

This is then the value to replace the LIMIT placeholder with using sed:

mutt -F <(sed "s/LIMIT/$(expr $(grep Message-ID INBOX.backup | wc -l) + 1)/" .backup.muttrc)

5 Food for Thought

Speaking of the Message-ID field, it would certainly be more reliable to track unique message identifiers to work out what new messages to download, and potentially also which ones to remove from the backup which might have been deleted from the server since the last run. That's probably to some extent what tools like OfflineIMAP do.

6 References