11 Apr 2021

# Fetch e-mail with mbsync in Emacs with a hydra to choose the channel

E-mail is my preferred way of communication, even if some say that e-mail is doomed.

I read my e-mail with Gnus in Emacs. The IMAP back-end in Gnus can sometimes be slow over the network and therefore lock Emacs while fetching. Another drawback of IMAP is the need to be on-line to access the e-mails on the server. Finally, having a local copy of all my messages is important for e-mail provider independence.

This is why I use mbsync/isync to get my e-mail for several accounts. This can happen outside of Emacs by calling the mbsync command which will use the appropriate configuration in .mbsyncrc to do its thing. I could put this on a cron job, but I prefer to run the e-mail fetching at my pace. Since my interface to the computer is Emacs, I want to be able to use mbsync from Emacs.

It shouldn't be difficult to write an Elisp function to call an external process running mbsync, but Dimitri Fontaine put together mbsync.el which does this cleanly logging output to a dedicated buffer and warning if things go wrong when trying to connect to servers.

Unfortunately, mbsync.el runs mbsync with the -a flag, which means that all channels (that is the e-mail accounts configured in .mbsyncrc) will be fetched. While this is a sane default, I sometimes want to fetch just one e-mail channel. The author of mbsync.el had the nice idea of providing a customizable variable to store the flags:

(defcustom mbsync-args '("-a")
"List of options to pass to the mbsync' command."
:group 'mbsync
:type '(repeat string))


We can therefore exploit this to change the flags. I wrote the following function to call mbsync for a particular channel:

(defun gd/mbsync-single-channel (channel-name)
"Fetch e-mail with mbsync for a single channel"
(interactive "smbsync channel: ")
(message "Fetching mail for channel %s" channel-name)
(let ((mbsync-args (list channel-name))
(mbsync-buffer-name (format "*mbsync %s*" channel-name)))
(mbsync)))


The function takes a string containing the channel name and will use it as the flag to call mbsync. I also change the name of the the buffer where mbsync.el will write the logs (mbsync-buffer-name is a variable defined in mbsync.el). Binding the variables with let leaves them as they were before calling the function.

This function can be called as an interactive command (with M-x), but can also be used to define one function for each e-mail channel:

(defun gd/mbsync-proton ()
(interactive)
(gd/mbsync-single-channel "proton"))
(defun gd/mbsync-fastmail ()
(interactive)
(gd/mbsync-single-channel "fastmail"))
(defun gd/mbsync-gmx ()
(interactive)
(gd/mbsync-single-channel "gmx"))
(defun gd/mbsync-garjola ()
(interactive)
(gd/mbsync-single-channel "garjola"))


I am not very happy with this approach, since there is lots of redundancy. Maybe I could use some macros to simplify this, but I am not proficient enough in Elisp yet.

With these functions, I can define a hydra which will provide a menu to select the e-mail channel to fetch.

(defhydra gd/hydra-mbsync (:timeout 4)
"Fetch e-mail with mbsync"
("p" gd/mbsync-proton  "proton")
("f" gd/mbsync-fastmail  "fastmail")
("x" gd/mbsync-gmx  "gmx")
("g" gd/mbsync-garjola "garjola"))

(global-set-key (kbd "<f9> O") 'gd/hydra-mbsync/body)


Now, when I type <f9> O`, the following menu appears in the mini-buffer.

And I can select which channels to fetch. Yes, channels in plural, because this is a persistent menu which stays there for 4 seconds (the timeout parameter of the hydra) and I can select several of the channels.