Two custom headers for mu4e

Tl;dr: you can jump to the custom headers part.

In this article I'll present two usefull (at least for me) headers for mu4e. But before this, I'll make a quick introduction on how I discovered this cool mail client.

Introduction

The date is 2006, the 18th april. At some point at night. I don't remember why I wasn't using my computer but the fact is: I was reading my emails directly on my provider webmail. Back in the time, that was one of the first roundcube version, hosted by Gandi. The first thing I loved -- and currently always enjoy -- with linux, is the possibility to use simpler, cleaner console software. That's why I quickly adopt alpine around 2005. But still, that night I was on roundcube. Reading my emails, deleting the non-interesting ones and keeping the others. I browse from my Inbox to the Trash, try to empty it. Doesn't work. Crap. Click again on Inbox and back again on Trash to reload the folder. Click on Empty folder. That's the very moment when shit happened. Some ajax request failed at some point and I'd just deleted my entire mail history. Obviously, the younger me was a bit unconscious and didn't keep any backup. #Yolo.

The day after, I discovered offlineimap (or something similar at that time) and promise myself to never do it again. Since that day, I always keep something like two or three different snapshots of all my inboxes. Of course, they growth with the year until a point where alpine was no more a viable solution. As it do all of its works directly on the imap server, browsing or searching through my ~25000 mails begin to take too long to be acceptable. Thus I look for a replacement and found my happyness in the mu project. Its aim is to provide an indexer for local email collections. But an emacs module is also provided as a side-project: the previously mentioned mu4e (mu for emacs). As I already have a local copy of my emails, thanks to offlineimap, mu was just the right tool to use. It's blazingly fast, has very nice search tricks, is able to retrieve and manage contacts even if they are not in my addressbook. A pleasure.

But it needs some little improvement to cover all of my needs.

The custom headers

These headers will be visible in the message view.

I begin to wrote a generic function to retrieve any mail header content from a mail stored somewhere in a maildir folder. I should have done something more lispy, but I know a little more sed than lisp and it works well so I stopped there:

(defun ed/get-mail-header (header-name path)
(replace-regexp-in-string
"[ \t\n]*$"
""
(shell-command-to-string
(concat "/usr/bin/sed -n '/^" header-name ":/I{:loop t;h;n;/^ /{H;x;s/\\n//;t loop};x;p}' " path " | sed -n 's/^" header-name ": \\(.*\\)$/\\1/Ip'"))))

To use it, you just have to call it like this, saying your email file is /home/toto/Mail/INBOX/cur/trololo: (ed/get-mail-header "x-spam-level" "/home/toto/Mail/INBOX/cur/trololo").

Mail client of your interlocutor

During a time I used Thunderbird and enjoyed a lot the display user agent addon. I cannot explain why I like it, but I found sometime interesting to know what mail client people where using (like their mobile, gmail etc.). Thus I try to get it on mu4e. After some lisp work, here are the required bits to achieve that:

(defun ed/get-origin-mail-system-header (msg)
(let ((path (or (mu4e-message-field msg :path) "")))
(if (or (string= path "")
(not (file-readable-p path)))
"no path found"
(let ((xmailer (ed/get-mail-header "x-mailer" path))
(useragent (ed/get-mail-header "user-agent" path)))
(if (string= xmailer useragent)
xmailer
(cond
((string= xmailer "") useragent)
((string= useragent "") xmailer)
(t (concat xmailer " (xmailer)\n" useragent " (user-agent)"))))))))

As you can see, all the hard work is done by the previous ed/get-mail-header function.

OpenPGP informations

The openpgp header is mainly added by the enigmail addon of Thunderbird. I like the idea behind it. We often add our PGP Id in our mail signatures, but it may be disturbing for non-technical people. With this header, you can send your id without poluting your signature. It's even better as you can use it either to just communicate you PGP id or an URL to your public key, allowing your recipients to download it.

We retrieve this header the exact same way as user-agent information:

(defun ed/get-openpgp-header (msg)
(let ((path (or (mu4e-message-field msg :path) "")))
(if (or (string= path "")
(not (file-readable-p path)))
"Mail file is not accessible"
(ed/get-mail-header "openpgp" path))))

Add these headers to the visible headers list

You must customize the mu4e-view-fields like this:

(add-to-list 'mu4e-header-info-custom
'(:useragent . (:name "User-Agent"
:shortname "UserAgt."
:help "Mail client used by correspondant"
:function ed/get-origin-mail-system-header)))
(add-to-list 'mu4e-header-info-custom
'(:openpgp . (:name "PGP Info"
:shortname "PGP"
:help "OpenPGP information found in mail header"
:function ed/get-openpgp-header)))

(setq mu4e-view-fields '(:from :to  :cc :subject :flags :date :maildir
:mailing-list :tags :useragent :attachments
:openpgp :signature :decryption))

You should also customize the mu4e-compose-hidden-headers variable to hide them from the composing buffer:

mu4e-compose-hidden-headers '("^References:" "^Face:" "^X-Face:"
"^Openpgp:" "^X-Draft-From:"
"^X-Mailer:" "^User-agent:")

What does it look like?

From: John Doe <john@doe.fr>
To: Me <you@know>
Subject: Prochaine réunion
Flags: seen, signed, attach
Date: ven. 23 sept. 2016 15:13:55 CEST
Maildir: /Posteo/INBOX
User-Agent: Alpine 2.20 (LNX 67 2015-01-07)
Attachments: [1]signature.asc(836)
PGP Info: id=ABCDEF0123456789[…];
preference=signencrypt
Signature: verified (Details)

Add theses headers in our own emails

Now that we are able to display these information from the emails we got, I think it's fair to play the game and to be sure we add these headers in the emails we send from mu4e.

The hardest header to add is the openpgp one, as we want to add it only if we're currently sending a signed email, and of course we want to send the right PGP ID for our current identity. Starting from the new Emacs 25, the later is as simple as setting the mml-secure-openpgp-sign-with-sender variable to t.

First we need a function, which will insert the right PGP ID for your current identity. As I use mu4e new context feature, for me this function look like:

(defun ed/insert-gpg-headers()
(save-excursion
(goto-char (point-min))
(let ((pgp-info
(cond
((string= (mu4e-context-name (mu4e-context-current)) "posteo")
"id=2EBD3E9457B6316DBD100ABCA714ECAC8C9CEE3D;\n url=https://etienne.depar.is/8c9cee3d-public.gpg\n")
((string= (mu4e-context-name (mu4e-context-current)) "umaneti")
"id=72D9729DF611D2BA65552E3D47E5AFEA8FB5F5D3;\n url=https://etienne.depar.is/8fb5f5d3-public.gpg\n")
((string= (mu4e-context-name (mu4e-context-current)) "iRaiser")
"id=F119A4DA3FB7343E590A83DCE9491C429882ECD9\n"))))
(insert (concat "Openpgp: " pgp-info)))))

Then we must declare two functions to be called when we declare that the current draft must be signed or signed-encrypted. Then we will replace the default sign and sign-encrypt key binding in order to use our new functions. This can be done with the following code:

(defun ed/sign-this-message ()
"Insert mml gpg command and gnupg header"
(interactive)
(ed/insert-gpg-headers)
(mml-secure-message-sign))

(defun ed/encrypt-this-message ()
"Insert mml gpg command and gnupg header"
(interactive)
(ed/insert-gpg-headers)
(mml-secure-message-sign-encrypt))

We must now bind these functions to the right key bindings. As we do not want to be dirty, we'll do that only locally in the current composing buffer.

(add-hook 'mu4e-compose-mode-hook
(lambda ()
(local-set-key (kbd "C-c <return> C-s") 'ed/sign-this-message)
(local-set-key (kbd "C-c <return> C-e") 'ed/encrypt-this-message)))

Finally, the easyer header to add is the mail-agent header. Because we'll add it to all our emails, without distinction. In fact, Emacs (or mu4e, or smtpmail I cannot say which of them) already attach the user-agent header. To cover all cases, we'll just add the x-mailer one. The mu4e-compose-mode-hook will now look like:

(add-hook 'mu4e-compose-mode-hook
(lambda ()
(set-fill-column 72)
(local-set-key (kbd "C-c <return> C-s") 'ed/sign-this-message)
(local-set-key (kbd "C-c <return> C-e") 'ed/encrypt-this-message)
(save-excursion
(goto-char (point-min))
(insert (concat "X-Mailer: mu4e " mu4e-mu-version "; emacs " emacs-version "\n")))))

Conclusion

This story moral is: you must think about your mail backups, period! You now know why I hate javascript, ajax and webmail in particular. I always fear something terrible will happen.

Regarding the two discussed custom headers, as I said before, I should have used a more pure lisp approach to fetch the mail headers. If you have anything to share about that, do not hesitate to publish it in the comments.

If you already use Emacs, you should definitely give mu4e a try! It worths it. If you don't but are a pgp user, please try to let your email client add the openpgp header. It's really helpfull to quickly found which key we must use to answer you.

Finally, I may have spoken about my mu4e-headers-fields configuration to make the headers view look like my good old alpine. But this article is already too long, thus maybe for another time.