Custom sorting of mu4e headers

2026 Jan 08

I love mu4e for dealing with email under Emacs. It’s a great package itself, but of course the killer feature is that it’s Emacs, and with a little Emacs Lisp you can make it do email how you want to do email. I mean I know you don’t want to do email, but still.

My email workflow

I use Fastmail, which I can recommend overall. I have only a few top-level folders:

Inbox
work
personal
Spam
Drafts
Sent
Archived
Trash

I have quite a few filters set up so that everything not to my work email (and a few other criteria) go into personal, and work mailing lists and other low-priority stuff go into work. Meanwhile lots of garbage gets ruthlessly sent straight to Trash. And Fastmail is quite good (both Type I and Type II good) at detecting Spam. Of course every outgoing message goes to Sent.

The one good thing about the years I spent in Gmail was acquiring the habit that emails are either not done (in the GTD sense) until they are put into Archive, or they are filed irreversibly into Archive at which time they are done done done. Every email has a binary state: not yet in Archive and pending action (including a mere reply), or put into Archive. In short, Inbox is enriched for high-priority messages because lower-priority messages go to work and personal. From each of those three, I step through messages and file them Archive after they are done.

This workflow, refined over two decades, works very well for me.

Finally, I have two virtual folders, mu4e searches that are saved as ’bookmarks,’ that I call Unread and Evacuate. The former shows me everything unread in Inbox, personal, and work, for rare times when I prefer to process them all at once. The latter shows me everything in those folders that is already read, but not yet sent to Archive. Pending action, in other words, waiting to be evacuated to Archive.

Thus, many times a day I can browse through Unread emails, quickly in mu4e or even on the Fastmail app on my phone, and triage away low-priority messages, immediately deal with urgent messages, and know that the ones I ignore for the time being are waiting for me to Evacuate later.

;;; "Unread" bookmark is everything not yet marked read
;;; except messages already filtered directly to Trash
;;; or automatically marked as Spam.
(defcustom jeh/mu4e-bookmark-unread "flag:unread AND NOT flag:trashed AND NOT maildir:/Spam"
  "String for mu4e search of 'Unread' messages.")

;;; "Evacuate" bookmark is kinda the converse of unread:
;;; message that have been marked as read, but not yet
;;; refiled into Archive. Pending action, in other words.
(defcustom jeh/mu4e-bookmark-evacuate
      (concat "flag:seen and "
              "not ("
                   " flag:trashed"
                   " or maildir:/Sent"
                   " or maildir:/Archive"
                   " or maildir:/Scheduled"
                   " or maildir:/Drafts"
                   " or maildir:/Trash"
                   " or maildir:/Spam"
              ")")
      "String for mu4e search for 'Evacuate' or pending
messages, read but not yet refiled in Archive.")

(setq mu4e-bookmarks
      `((:name "Inbox" :key ?i :query "maildir:/Inbox")
        (:name "Work" :key ?w :query "maildir:/work")
        (:name "Personal" :key ?p :query "maildir:/personal") 
        (:name "Drafts" :key ?d :query "maildir:/Drafts")
        (:name "Spam" :key ?s :query "maildir:/Spam")
        (:name "Trash" :key ?t :query "maildir:/Trash")
        (:name "Sent" :key ?n :query "maildir:/Sent")
        (:name "Archive" :key ?a :query "maildir:/Archive")
        (:name "Evacuate" :key ?e :query ,jeh/mu4e-bookmark-evacuate)
        (:name "Unread" :key ?u :query ,jeh/mu4e-bookmark-unread)))

I’ve had this working nicely in mu4e for a couple years.

Custom sorting depending on “folder” (or search)

But something annoyed me!

Archive and Sent have tens of thousands of messages, while all the other folders usually have less than a dozen. (Honest to god, this system keeps me at a calming steady state of Inbox Zero with a resolution of 24–48 hours, which my Special Brain relies heavily on for mental health.)

My preference in the mu4e “headers” buffer is

  • to see newest messages first in Archive, Sent, and in arbitrary searches (which can have hundreds or thousands of hits), in order to prioritize recency and relevance, but
  • to see oldest messages first in Inbox, personal, and work, in order to prioritize pending tasks by age.

Toggling sorting manually was so intolerably not automated by Emacs!

So I just wrote a couple of lines of Emacs Lisp. The key symbols to know are mu4e-search-change-sorting and mu4e-search-bookmark-hook.

;;; Inbox-type folders should be sorted 'ascending
(defcustom jeh/mu4e-reverse-sort-bookmarks `(
                                             "maildir:/Inbox"
                                             "maildir:/work"
                                             "maildir:/personal"
                                             "maildir:/Drafts"
                                             "maildir:/Spam"
                                             "maildir:/Trash"
                                             ,jeh/mu4e-bookmark-evacuate
                                             ,jeh/mu4e-bookmark-unread
                                             )
  "Sort these searches `ascending', by OLDEST at top.")

(defun jeh/mu4e-set-sort-order-by-bookmark (search)
  "Set sort for searches to descending by date, unless the
search was a member of jeh/mu4e-reverse-sort-bookmarks
in which case sort ascending by date."
  (if (member search jeh/mu4e-reverse-sort-bookmarks)
      (mu4e-search-change-sorting :date 'ascending)
    (mu4e-search-change-sorting :date 'decending)
    ))

(add-hook 'mu4e-search-bookmark-hook #'jeh/mu4e-set-sort-order-by-bookmark)

Ahhhhhhh. That feels better.

tags: emacs org-mode

Created with org-static-blog | GNU Emacs 30.1 Org mode 9.7.11