index all rss twitter github linkedin email

Álvaro Ramírez

28 May 2018 Trying out mu4e and offlineimap

mu4e.png

Managing Email from Emacs. Surely that's crazy-talk, but hey… let's give it a try.

Install offlineimap

Need to sync via imap. Use offlineimap. I'm on macOS, so homebrew is king for installing:

brew install offlineimap

Before can configure offlineimap, we'll need to handle a few things first.

Get a cert fingerprint

Use openssl for getting a certificate fingerprint. From offlineimap's FAQ:

SSL_CERT_DIR="" openssl s_client -connect imap.migadu.com:993 < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -text -in /dev/stdin

Should give you something like:

SHA1 Fingerprint=AA:BB:CC:DD:EE:DD:FF:AA:00:AA:2A:AA:AA:AA:A8:20:80:AA:A2:AA

Encrypt password

Offlineimap can read passwords in plain text in its .offlineimaprc config file, but that's yuckie. Let's encrypt the password and use gnupg for that. Install it:

brew install gnupg

If you haven't already, generate a key

gpg --full-gen-key

Generate an offlineimap account password file.

echo "YourPassword" | gpg --encrypt --recipient "Your Name" -o ~/.offlineimap_accountname.gpg

Python password wrapper

Based on Fabian's Encrypt OfflineIMAP and msmtp password with GnuPG, I created ~/.read_password.py with:

import os
import subprocess

def read_password(path):
  return subprocess.check_output(["gpg\n", "--quiet\n", "--batch\n", "-d\n", os.path.expanduser(path)]).strip()

ps. Alternatively, see The homely Mutt's section to store password in macOS's keychain.

Configure offlineimap

Offlineimap uses ~/.offlineimaprc for configuration. We now have all we need to put the configuration together:

[general]
accounts = Personal

# Load this python file.
pythonfile = ~/.read_password.py

[Account Personal]
localrepository = Personal-Local

remoterepository = Personal-Remote

# After syncing, let mu index it.
postsynchook = mu index --maildir ~/stuff/active/Mail

# Sync imap every 5 minutes.
autorefresh = 5

# Alternate between 10 quick syncs and full syncs.
quick = 10

[Repository Personal-Local]
type = Maildir
localfolders = ~/stuff/active/Mail/Personal

[Repository Personal-Remote]
type = IMAP
remotehost = some.imap.host.com
remoteuser = your_user_name

# Use function defined in .read_password.py to read the password.
remotepasseval = read_password("~/.offlineimap_personal_account_password.gpg")

# Use the SHA1 fingerprint retrieved with openssl.
cert_fingerprint = aabbccddeeddffaa00aa2aaaaaaaa82080aaa2aa

Cert file

You can use macOS's certificates from Keychain Access -> System Roots -> Certificates, select all, and ⌘-⇧-e (for export items). Save to ~/certs.pem and use offlineimap configutation:

sslcacertfile = /path/to/certs.pem

Another option is executing lib/mk-ca-bundle.pl from curl's tarball to generate ca-bundle.crt, using certdata.txt from Mozilla's source tree.

Install mu4e

Manually modified mu4e recipe to pick up my Emacs binary. TIL about homebrew's edit command:

brew edit mu

Changed the one line:

  • ENV["EMACS"] = "no" if build.without? "emacs"
  • ENV["EMACS"] = "/Users/alvaro/homebrew/Cellar/emacs-plus/26.1-rc1_2/bin/emacs"

Finally installed mu4e:

brew install mu

Configure mu4e

Lastly, configure mu4e:

(add-to-list 'load-path
             (expand-file-name "~/homebrew/share/emacs/site-lisp/mu/mu4e"))
(use-package mu4e
  :config
  ;; Update mail using 'U' in main view:
  (csetq mu4e-get-mail-command "offlineimap")
  (csetq mu4e-view-show-addresses t)
  (csetq mu4e-attachment-dir (expand-file-name "~/Downloads/"))
  (csetq mu4e-maildir "~/stuff/active/Mail")
  (csetq mu4e-html2text-command "w3m -T text/html") ;; alternatively "textutil -stdin -format html -convert txt -stdout"
  (csetq mu4e-user-mail-address-list '("myself@domain1.com"
                                       "myself@domain2.com"))
  (csetq mu4e-context-policy 'pick-first)
  (csetq mu4e-compose-context-policy 'always-ask)
  (setq mu4e-contexts
        (list
         (make-mu4e-context
          :name "domain1"
          :enter-func (lambda () (mu4e-message "Entering context myself@domain1.com"))
          :leave-func (lambda () (mu4e-message "Leaving context myself@domain1.com"))
          :match-func (lambda (msg)
                        (when msg
                          (mu4e-message-contact-field-matches
                           msg '(:from :to :cc :bcc) "myself@domain1.com")))
          :vars '((user-mail-address . "myself@domain1.com")
                  (user-full-name . "Álvaro Ramírez")
                  (mu4e-sent-folder . "/Domain1/Sent")
                  (mu4e-drafts-folder . "/Domain1/Drafts")
                  (mu4e-trash-folder . "/Domain1/Trash")
                  (mu4e-compose-signature . nil)
                  (mu4e-compose-format-flowed . nil)))
         (make-mu4e-context
          :name "domain2"
          :enter-func (lambda () (mu4e-message "Entering context myself@domain2.com"))
          :leave-func (lambda () (mu4e-message "Leaving context myself@domain2.com"))
          :match-func (lambda (msg)
                        (when msg
                          (mu4e-message-contact-field-matches
                           msg '(:from :to :cc :bcc) "myself@domain2.com")))
          :vars '((user-mail-address . "myself@domain2.com")
                  (user-full-name . "Álvaro Ramírez")
                  (mu4e-sent-folder . "/Domain2/Sent")
                  (mu4e-drafts-folder . "/Domain2/Drafts")
                  (mu4e-trash-folder . "/Domain2/Trash")
                  (mu4e-compose-signature . nil)
                  (mu4e-compose-format-flowed . nil))))))

Authinfo

Create an ~/.authinfo file for sendmail authentication with:

machine smtp.host1.com login account1@host1.com password somepassword1
machine smtp.host2.com login account2@host2.com password somepassword2

Encrypt ~/.authinfo with M-x epa-encrypt-file. Keep ~/.authinfo.gpg and delete ~/.authinfo.