Hack and /: Cleaning Your Inbox with Mutt

Teach Mutt yet another trick: how to filter messages in your Inbox with a simple macro. By Kyle Rankin

I'm a longtime Mutt user and have written about it a number of times in Linux Journal. Although many people may think it's strange to be using a command-line-based email client in 2018, I find a keyboard-driven email client so much more efficient than clicking around in a web browser. Mutt is extremely customizable, which presents a steep learning curve at first, but now that I'm a few decades in, my Mutt configuration is pretty ideal and fits me like a tailored suit.

Of course, as with any powerful and configurable tool, every now and then I learn of a new Mutt feature that improves my quality of life dramatically. In this case, I was using an email system that didn't offer server-side filters. Because I was a member of many different email groups and aliases, this meant that my Inbox was flooded with emails of all kinds, and it became difficult to filter through all the unimportant email I wanted to archive with the emails that demanded my immediate attention.

There are many ways to solve this problem, some of which involve tools like offlineimap combined with filtering tools. With email clients like Thunderbird, you also can set up filters that automatically move email to other folders every time you sync. I wanted a similar system with Mutt, except I didn't want it to happen automatically. I wanted to be able to press a key first so I could confirm what was moving. In the process of figuring this out, I discovered a few gotchas I think other Mutt users will want to know about if they set up a similar system.

Tagging Emails

The traditional first step when setting up a keyboard macro to move email messages based on a pattern would be to use Mutt's tagging-by-pattern feature (by default, the T key) to tag all the messages in a folder that match a certain pattern. For instance, if all of your cron emails have "Cron Daemon" in the subject line, you would type the following key sequence to tag all of those messages:


TCron Daemon<enter>

That's the uppercase T, followed by the pattern I want to match in the subject line (Cron Daemon) and then the Enter key. If I type that while I'm in my Mutt index window that shows me all the emails in my Inbox, it will tag all of the messages that match that pattern, but it won't do anything with them yet. To act on all of those messages, I press the ; key (by default), followed by the action I want to perform. So to save all of the tagged email to my "cron" folder, I would type:


;s=cron<enter>

That's ; followed by the s key to save, followed by the name of the folder to save to, where =cron means "the folder named cron that sits under the Inbox". To combine all of this into a macro so I can trigger this action by pressing, say, .c, I would add the following to my Mutt configuration file:


macro index .c "TCron Daemon<enter>;s=cron<enter>"

Or, if you want to make it more portable (in case you remapped your save command to another key), you could do this:


macro index .c "TCron Daemon<enter><tag-prefix>
↪<save-message>=cron<enter>"

Of course, if you're cleaning out a lot of messages in your Inbox, you'll probably have a lot of different patterns to match. For instance, I want to move all of the DMARC report messages that are sent to the dmarc-reports email address, so I'm going to add another pattern to this macro that will save all of those messages to my dmarc folder. By itself, the macro would look like this:


macro index .c "T~Cdmarc-reports<enter><tag-prefix>
↪<save-message>=dmarc<enter>"

The most important difference here is that for my tagging pattern, instead of just matching on dmarc-reports, which would match only the subject line, I typed ~C in front of it, which tags all messages that have "dmarc-reports" in the To: or CC: headers. The combined macro just combines the two lists of keypresses one after the other and looks like this:


macro index .c "TCron Daemon<enter><tag-prefix><save-message>
↪=cron<enter>T~Cdmarc-reports<enter><tag-prefix><save-message>
↪=dmarc<enter>"

The Problem

The above macro has a subtle problem though, and unless you have set up a Mutt macro like this in the past, you may not notice it. In fact, the first couple times you run the macro, it may seem like it works—as long as there are matching messages in your Inbox. The problem occurs if you don't have any matching messages. The way that Mutt interprets this macro, if you don't have any matching messages, it still will happily apply any commands that follow the <tag-prefix> command—only to whatever message the cursor is currently under! Luckily I was just moving messages around, but if you told Mutt to delete tagged messages, they would be gone for good!

The solution here is to use a special Mutt command called <tag-prefix-cond> instead of <tag-prefix>. This tells Mutt to execute the command following <tag-prefix-cond> only if Mutt actually has any messages tagged. You then wrap the command with <end-cond> to tell Mutt that conditional command is completed. So for a simple macro, I would replace:


macro index .c "TCron Daemon<enter><tag-prefix><save-message>
↪=cron<enter>"

with:


macro index .c "TCron Daemon<enter><tag-prefix-cond>
↪<save-message>=cron<enter><end-cond>"

As you can see, I wrapped the entire <save-message> command up inside this conditional block. If I gave the same treatment to the full macro, I would convert:


macro index .c "TCron Daemon<enter><tag-prefix><save-message>
↪=cron<enter>T~Cdmarc-reports<enter><tag-prefix>
↪<save-message>=dmarc<enter>"

to:


macro index .c "TCron Daemon<enter><tag-prefix-cond>
↪<save-message>=cron<enter><end-cond>T~Cdmarc-reports<enter>
↪<tag-prefix-cond><save-message>=dmarc<enter><end-cond>"

Now when I want to add a new filter for my Inbox, I can test the tag command one time within Mutt by hand to confirm it does what I expect, and then append it to my macro. And now when I load my Inbox, I can press a simple key and perform those filters. If you want this to be automatic, you would just set up a folder-hook statement for your Inbox folder that uses the Mutt push command to press all of the keys in the above macro.

About the Author

Kyle Rankin is a Tech Editor and columnist at Linux Journal and the Chief Security Officer at Purism. He is the author of Linux Hardening in Hostile Networks, DevOps Troubleshooting, The Official Ubuntu Server Book, Knoppix Hacks, Knoppix Pocket Reference, Linux Multimedia Hacks and Ubuntu Hacks, and also a contributor to a number of other O'Reilly books. Rankin speaks frequently on security and open-source software including at BsidesLV, O'Reilly Security Conference, OSCON, SCALE, CactusCon, Linux World Expo and Penguicon. You can follow him at @kylerankin.

Kyle Rankin