This page looks best with JavaScript enabled

Obtain emails header in notmuch

 ·  ☕ 3 min read  ·  ✍️  Firmin Martin

Introduction

It’s been half a month that I’m gradually writing an Emacs package for notmuch email notification. Although it works fine so far, it misses an important feature which consists to display the subject and the sender name on the notification instead of merely saying “2 new messages since last refresh”.

Fortunately, probably because the development of the notmuch Emacs frontend, notmuch can speak Lisp S-expression.

Notmuch show

To retrieve header from emails, we need the command notmuch show. Moreover, since we have no interest on email body and other emails that mismatch the query, we need the options --body=false and --entire-thread=false. As stated above, we also want S-expression as output format, e.g. --format=sexp. Putting together:

1
notmuch show --body=false --entire-thread=false --format=sexp <search-term>

where search-term corresponds, in notmuch jargon, to the query expression.

From notmuch show documentation, a thread corresponds to a nested structure. For testing purpose, I wrote to myself several emails to obtain two threads of the following structure (with the sent time to better identifying them later):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
|- email 1 (12:32:37)
|  |
|  |-- email 2 (12:32:56)
|  |  |
|  |  |- email 3 (12:33:18)
|  |
|  |-- email 4 (12:33:34)
|
|- email 5 (12:44:34)
   |
   |- email 6 (12:45:41)

… and use an appropriate search term to filter out exactly these threads.

The notmuch package already provides the function notmuch-call-notmuch-sexp to convert the shell output to Emacs Lisp S-expression.1 We can thus translate the previous shell command into

1
2
(notmuch-call-notmuch-sexp
     "show" "--body=false" "--entire-thread=false" "--format=sexp" search-term)

But! My instinct tells me that we could have better. So, searching which functions actually call notmuch-call-notmuch-sexp brings me to the library notmuch-query which contains lots of useful functions. In particular, the function call above can, kind of2, be simplify to

1
(notmuch-query-get-threads (list search-term))

Notmuch thread structure

In notmuch, an email corresponds to a property list (plist) with the following fields id, match, excluded, filename, timestamp, date_relative, tags, body, crypto, headers. Needless to say, we will pay particular attention on headers. tags and body could be useful for further development. The command above returns me six plists surrounded by countless parentheses. To understand better the structure, I replaced the plists by the sent time:

1
2
3
4
5
6
7
((("12:44:34"
   (("12:45:41" nil))))

 (("12:32:37"
   (("12:32:56"
     (("12:33:18" nil)))
    ("12:33:34" nil)))))

Comparing to the threads I artificially created, it’s pretty much the same.

Flatten the S-expression

The tree structure is not relevant for our purpose, so we want to flatten it as a list of plist. We actually don’t want to parse the S-expression ourselves, as stated above, the notmuch-query library provides some functions we can use directly. It can be done by

1
2
(notmuch-query-map-threads #'identity
			   (notmuch-query-get-threads (list search-term)))

And as it is actually a map function, to obtain a list of headers we can simply define the following function.

1
2
3
4
(defun notmuch-notify--show-headers (search-term)
  (notmuch-query-map-threads
   (lambda (p) (plist-get p :headers))
   (notmuch-query-get-threads (list search-term))))

Conclusion

Having emails header given a search term, we can now easily introduce subject and email sender along with the notification message.

See my work-in-progress package notmuch-notify for more information!


  1. Equivalently, one can use (read (shell-command-to-string ...)) for the same purpose. But notmuch-call-notmuch-sexp is more robust as it offers error handling. ↩︎

  2. It misses the arguments --body=false and --entire-thread=false, but they are actually not so important as they won’t save us much performance. ↩︎


Firmin Martin
WRITTEN BY
Firmin Martin

What's on this Page