24 December 2021

I’ve tried many times to enable mu4e and offlineimap in msys2 environment, but no result. Although the author of mu4e provides the unofficial package files for msys2, but I still get a compile failure. Worse off, offlineimap is not working in msys2 environment.

I’ve configured emacs under WSL2 and I feel little difference from msys2 mingw64 emacs. And It is also quite easy to configure mu4e and offlineimap for Manjaro.

This guide will lead you to configure mu4e and offlineimap in emacs, and securely encrypt the email credentials.

1. install

Supposing you have configured Manjaro WSL2 and then open Manjaro WSL2 shell:

# install pip
sudo pacman -S python-pip
# install python gpg
sudo pip install python-gnupg getpass4
# offlineimap
sudo pacman -S offlineimap
# mu4e
wget https://github.com/djcb/mu/archive/1.6.10.tar.gz
tar zxvf 1.6.10.tar.gz
cd mu-1.6.10
./augogen.sh
make
sudo make install

2. configure Emacs

Emacs config extracted from my .emacs:

;; get load path
(eval-and-compile
  (defun mu4e-load-path ()
    (cond ((eq system-type 'darwin)
           "/usr/local/Cellar/mu/1.0_1/share/emacs/site-lisp/mu/mu4e")
          ((eq system-type 'windows-nt)
           "/usr/local/share/emacs/site-lisp/mu4e")
          ((eq system-type 'gnu/linux)
           "/usr/local/share/emacs/site-lisp/mu4e/"))))

(use-package mu4e
  :ensure nil
  :functions (mu4e-compose-reply
              mu4e~view-quit-buffer)
  :defines (mu4e-html2text-command
            mu4e-mu-binary
            mu4e-get-mail-command
            mu4e-update-interval
            mu4e-hide-index-messages
            mu4e-use-fancy-chars
            mu4e-view-show-images
            mu4e-view-fields
            mu4e-headers-fields
            mu4e-compose-cite-function
            mu4e-compose-reply-recipients
            mu4e-headers-mode-map
            mu4e-compose-mode-map
            mu4e-view-mode-map
            shr-color-visible-luminance-min
            shr-color-visible-distance-min)
  :custom
  (mu4e-compose-reply-recipients 'sender)
  (mu4e-compose-signature-auto-include nil)
  (mu4e-index-update-in-background nil) ;; prompt for gpg passwd
  :commands (mu4e mu4e-compose-new)
  :load-path (lambda () (list (mu4e-load-path)))
  :config
  (require 'sendmail)
  ;; turn html email to lighter color in dark theme
  (require 'mu4e-contrib)
  (setq mu4e-html2text-command 'mu4e-shr2text)
  (setq shr-color-visible-luminance-min 60)
  (setq shr-color-visible-distance-min 5)
  (setq shr-use-colors nil)
  (advice-add #'shr-colorize-region :around
              (defun shr-no-colourise-region (&rest ignore)))

  (require 'org-mu4e) ;; capture link
  (add-to-list 'Info-additional-directory-list "/usr/local/share/info")
  (setq mu4e-mu-binary "/usr/local/bin/mu")
  (setq mail-user-agent 'mu4e-user-agent)
  ;; Fetch mail by offlineimap
  (setq mu4e-get-mail-command "offlineimap -c ~/.offlineimaprc -u quiet")
  ;; Fetch mail in 60 sec interval
  (setq mu4e-update-interval 300)
  ;; hide indexing messages from minibuffer
  (setq mu4e-hide-index-messages t)
  (setq mu4e-use-fancy-chars nil)
  (setq mu4e-view-show-images t)
  ;; configure view fields
  (setq mu4e-view-fields
        '(:subject :from :to :cc :date :mailing-list
                   :attachments :signature :decryption))
  (setq mu4e-headers-fields
        '( (:human-date    .   12)
           (:flags         .    6)
           (:from          .   22)
           (:subject       .   nil)))
  (setq mu4e-compose-cite-function 'mu-cite-original)
  (add-hook 'mu4e-view-mode-hook 'visual-line-mode)
  (add-hook 'mu4e-compose-mode-hook 'orgalist-mode)
  (add-hook 'mu4e-compose-mode-hook (lambda ()
                                      (auto-fill-mode -1)))
  ;; setup shortcuts
  (setq mu4e-maildir-shortcuts
        '( ("/outlook/Inbox"    . ?i)
           ("/outlook/Sent"     . ?s)))
  ;; smtp send settings
  (setq send-mail-function 'smtpmail-send-it
        smtpmail-stream-type 'starttls
        smtpmail-smtp-service 587))

3. add .offlineimaprc

create ~/.offlineimaprc replace _your_email_ with your email address (for example: kimim@outlook.com ):

[general]
accounts = outlook
maxsyncaccounts = 3
pythonfile = ~/passwd_prompt.py

[Account outlook]
localrepository = outlook-Local
remoterepository = outlook-Remote
utf8foldernames = True

[Repository outlook-Local]
type = Maildir
localfolders = ~/.mail/outlook

[Repository outlook-Remote]
type = IMAP
remotehost = imap.outlook.com
remoteuser = _your_email_
remotepasseval = get_authinfo_password()
ssl = true
sslcacertfile = /etc/ssl/cert.pem
maxconnections = 4
realdelete = yes

4. add python password prompt script

create ~/passwd_prompt.py (copied from Seth Kenlon):

#!/usr/bin/env python
# by Seth Kenlon
# GPLv3

import os
import gnupg
import getpass
from pathlib import Path

def get_api_pass():
    homedir = str(Path.home())
    gpg = gnupg.GPG(gnupghome=os.path.join(homedir, ".gnupg"), use_agent=True)
    passwd = getpass.getpass(prompt="Remote: Enter password: ", stream=None)

    with open(os.path.join(homedir, 'pass.gpg'), 'rb') as f:
        apipass = (gpg.decrypt_file(f, passphrase=passwd))

        f.close()

    return str(apipass)

def get_authinfo_password():
    authinfo = os.popen("gpg -q --no-tty -d ~/pass.gpg").read()
    return authinfo

if __name__ == "__main__":
    apipass = get_api_pass()
    print(apipass)
    apipass = get_authinfo_password()
    print(apipass)

If you use get_api_pass() inside .offlineimaprc, you can enable password prompt filter. It is important to set the prompt in above code as “Remote: Enter password: ”, because it is used by mu4e to trigger emacs to ask for password:

(defvar mu4e~get-mail-password-regexp "^Remote: Enter password: $"
  "Regexp to match a password query in the `mu4e-get-mail-command' output.")

If you use get_authinfo_password(), the password extraction is done by gpg, thus you do not need to add above emacs configuration. This function is borrowed from Gábor Melis.

5. add encrypted password file: pass.gpg

Use emacs to find a new file ~/pass.gpg, type in your password, and save with master password protection.

6. add smtp send credentials: .authinfo.gpg

Use emacs to find a new file ~/.authoinfo.gpg, type in following line:

machine smtp.outlook.com login _your_email_ port 587 password _your_passwd_

Replace _your_email_ with your email address, and replace _your_passwd_ with your password.

7. download email

Execute in Manjaro WSL2 shell:

cd ~
offlineimap

8. initialize mu4e

Initialize mu and index your emails:

mu init -m ~/.mail --my-address=_your_email_
mu index

Replace _your_email_ with your email address.

9. have a try

Now type M-x mu4e, emacs will show mu4e dashboard for you. j i will show the inbox and C will create a new email for you. You may need to type the master password for sending and updating email. But your email credential is not stored in file system as plain text.

10. Limitation

When mu4e triggers offlineimap to update email, you need to type in the master password for offlineimap password, because we cannot keep the password for each invoke of offlineimap.