MC's Journal

mu4e — a powerful Emacs mail client

I've been using Gnus as my mail and Usenet News client for over 15 years. For a long time I kept all my mail in local MH folders, updating them with fetchmail. Then I moved to remote IMAP folders with sweet, sweet server-side sorting courtesy of the Sieve sorting language.

Gnus is great a News client but leaves something to be desired when it comes to mail, especially when it comes to search abilities. I've managed so far by SSHing to the server and grepping over the Maildir backend but that, of course, requires SSH access. Naturally I don't have SSH access on $DAYJOB's Exchange server, so grep searching is right out and I had to cope with webmail for searching. Luckily for me, they didn't turn off IMAP on the Exchange server so I can still use Gnus for ordinary mail tasks.

Enter mu4e, a very customisable Emacs-based mail client which works with the very fast mu mail searching tool.

Together with offlineimap and the goodness of the Xapian search engine on which mu is built I now have very quick search available locally.

Running offlineimap on both work and play mail took a while. ~90,000 mail messages covering 1.6 GiB! Actually working with mu4e is quick enough, though. Impressively quick.

Instead of using Emacs' own smtpmail for sending messages, I opted to use the nice msmtp mail sending client and some Emacs Lisp to choose which server to send through depending on the From: line in the message I'm currently composing. See below.

Here's my inital mu4e configuration, subject to change. Some mail addresses has been removed or made slightly more obscure because, you know, spam.

(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e")
(autoload 'mu4e "mu4e" "mu for Emacs." t)
(setq mu4e-date-format-long "%Y-%m-%d %H:%M:%S")
(setq mu4e-headers-date-format "%y%m%d %H:%M:%S")
(setq mu4e-view-show-images t)

;; Silly mu4e only shows names in From: by default. Of course we also
;; want the addresses.
(setq mu4e-view-show-addresses t)

;;; Set defaults.
(setq mu4e-sent-folder (concat "/hack/sent." (format-time-string "%Y-%m" (current-time))))
(setq mu4e-drafts-folder "/hack/draft")
(setq mu4e-trash-folder "/hack/trash")

;; Re-index every 15 minutes.
(setq mu4e-update-interval (* 10 60))

;;; Bookmarks list demands mu4e loaded.
(require 'mu4e)

;;; Create new bookmarks to quickly visit an INBOX and show only
;;; flagged and unread messages.

(add-to-list 'mu4e-bookmarks
             '("maildir:/hack/INBOX flag:flagged OR maildir:/hack/INBOX flag:unread" "Unread or flagged in hack.org INBOX" ?h))

(add-to-list 'mu4e-bookmarks
             '("maildir:/$dayjob/INBOX flag:flagged OR maildir:/$dayjob/INBOX flag:unread" "Unread or flagged in $DAYJOB INBOX" ?s))

(setq mu4e-user-mail-address-list
      '(
        "long list of e-mail addresses deleted for spam harvesting
      reasons."
      ))

;;; Default html2text is no good. Can't use shr since I don't link
;;; Emacs with libxml2. So use w3m instead.
(setq mu4e-html2text-command "w3m -I utf8 -O utf8 -T text/html")

;;; FIXME: Should depend on what folder I'm replying from.
(setq mu4e-compose-signature "MC, http://hack.org/mc/\nIRC: mchack @ Freenode, OFTC\nTwitter: @mchackorg")

(defun mc-set-from-address ()
  "Set the From address based on where the folder of the parent,
if this is a reply. Otherwise, use default."
  (setq user-mail-address
        (if mu4e-compose-parent-message
          (cond
           ((string-match "\/slu.*" (mu4e-message-field mu4e-compose-parent-message :maildir))
            "michael.cardell.widerkrantz@$dayjob.se")
           (t "mc at the domain hack.org"))
          "mc at the domain hack.org")))

(add-hook 'mu4e-compose-pre-hook 'mc-set-from-address)

(defun mc-set-archive-folder ()
  "Set archive and draft folder name based on time and which mail server we use."
  (interactive)
  (let ((from-address (message-field-value "From"))
        (to-address (message-field-value "To")))
    (cond
     ((string-match "mc at the domain hack.org" from-address)
      (setq mu4e-sent-folder
            (concat "/hack/sent." (format-time-string "%Y-%m" (current-time))))
      (setq mu4e-drafts-folder "/hack/draft"))
     ((string-match "michael.cardell.widerkrantz@$dayjob.se" from-address)
            (setq mu4e-sent-folder
                  (concat "/$dayjob/sent." (format-time-string "%Y-%m" (current-time))))
            (setq mu4e-drafts-folder "/$dayjob/draft"))
     )))

(add-hook 'mu4e-compose-mode-hook 'mc-set-archive-folder)

;; Borrowed from http://ionrock.org/emacs-email-and-mu.html
(defun choose-msmtp-account ()
  "Choose account label to feed msmtp -a option based on From
header in Message buffer. This function must be added to
message-send-mail-hook for on-the-fly change of From address
before sending message since message-send-mail-hook is processed
right before sending message."
  (if (message-mail-p)
      (save-excursion
        (let*
            ((from (save-restriction
                     (message-narrow-to-headers)
                     (message-fetch-field "from")))
             (account
              (cond
               ((string-match "mc at the domain hack.org" from) "hack")
               ((string-match "michael.cardell.widerkrantz@$dayjob.se" from) "$dayjob")
               )))
             (setq message-sendmail-extra-arguments (list '"-a" account))))))

(setq message-sendmail-envelope-from 'header)
(add-hook 'message-send-mail-hook 'choose-msmtp-account)

(defun mc-message-mode ()
  "Define shortcuts to sign or encrypt a message."
  (define-key message-mode-map (kbd "\C-cp") 'mml-secure-message-sign-pgpmime)
  (define-key message-mode-map (kbd "\C-ce") 'mml-secure-message-sign-encrypt))

(add-hook 'message-mode-hook 'mc-message-mode)

(setq message-send-mail-function 'message-send-mail-with-sendmail
      sendmail-program "/usr/local/bin/msmtp")