July 10, 2020

Mail in Emacs with mu4e and mbsync

Intro

This post is the second part about GnuPG, password management, email, signing and encrypting emails and git commit signing. The first posts talked about GnuPG key generation, password management using pass, how to sign git commits and how Emacs connects with most of it. This post will focus on how to manage mails in Emacs with Mu4e and how to signing and encrypting messages with GnuPG. As the posts cover a lot of ground step by step instructions are not desirable. Links to more detailed resources can be found in each section. The main goal is to provide a quick but informative overview and give inspiration for further research.

The big picture is. Use mbsync to retrieve mails using imap. Mu4e for "Maildir indexer/searcher and Emacs client (mu4e)". All cridentials that are related to imap and SMTP are stored in pass's password store and are version controlled using git. GnuPG keys are used to encrypt the password store and to signing and encryting emails.

The layout of the post:

  • Environment; versions of the software (minus the ones already described in the first post).
  • Mbsync; how to configure mbsync and how to take advantage of pass.
  • Mu4e; Emacs config and how to sign and encrypt emails.

If things are unclear, please contact me via twitter!

Environment

Version of the software in use. OS, GnuPG, Pass and Emacs versions can be found in the first post.

#+BEGIN_SRC shell :results output code
  mbsync --version
#+END_SRC

#+RESULTS:
#+begin_src shell
isync 1.3.1
#+end_src

#+BEGIN_SRC shell :results output code
  mu --version
#+END_SRC

#+RESULTS:
#+begin_src shell
mu (mail indexer/searcher) version 1.4.10
Copyright (C) 2008-2020 Dirk-Jan C. Binnema
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
#+end_src

Mbsync

More details about isync/mbsync can be found here:

Install

mbsync/isync exists in Arch standard repos.

pacman -S isync

Config files

The config is more or less stolen from the Arch Wiki. PassCmd is using long arguments instead of short ones. Just to make things more describing.

Note that "PassCmd" gpg files is located in the pass password store.

#+BEGIN_SRC shell :results output code
  cat ~/.mbsyncrc
#+END_SRC

#+RESULTS:
#+begin_src shell
IMAPAccount gmail
Host imap.gmail.com
User jherrlin@gmail.com
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.password-store/mbsync/gmail.gpg"
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
Subfolders Verbatim
Path ~/.mail/gmail/
Inbox ~/.mail/gmail/Inbox

Channel gmail
Master :gmail-remote:
Slave :gmail-local:
Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/Starred" "[Gmail]/All Mail"
Create Both
SyncState *


IMAPAccount lnu
Host imap.gmail.com
User jh222jx@student.lnu.se
PassCmd "gpg2 --quiet --for-your-eyes-only --no-tty --decrypt ~/.password-store/mbsync/lnu.gpg"
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore lnu-remote
Account lnu

MaildirStore lnu-local
Subfolders Verbatim
Path ~/.mail/lnu/
Inbox ~/.mail/lnu/Inbox

Channel lnu
Master :lnu-remote:
Slave :lnu-local:
Create Both
SyncState *
#+end_src

Lets create the mbsync/gmail and mbsync/lnu files.

pass insert mbsync/gmail
pass insert mbsync/lnu

The layout of the file should be just the password on the first row and nothing more.

#+BEGIN_SRC shell :results output code
  gpg --decrypt ~/.password-store/mbsync/gmail.gpg
#+END_SRC

#+RESULTS:
#+begin_src shell
<PASSWORD>
#+end_src

Create needed folders.

mkdir -p ~/.mail/gmail ~/.mail/lnu

And sync all configs.

mbsync -a

Now all of your emails should be in your ~/.mail dir.

Mu4e

Here are some resources.

Install

Mu4e is installed from aur using:

mkdir -p ~/aur
cd $_
git clone https://aur.archlinux.org/mu.git
cd mu
makepkg -si

Init

Mu points to the same directory as mbsync. Before usage indexing needs to be done.

mu init --maildir=~/.mail --my-address=jherrlin@gmail.com --my-address=jh222jx@student.lnu.se
mu index

Emacs config

This is more or less all of the config for using mu4e in Emacs.

I will only cover the parts important to this post.

(setq auth-sources '(password-store)) and (auth-source-pass-enable) tells Emacs that passwords for smtp is in the pass password store. auth-source-pass looks for credentials in a certain order within the password store. The file that is found for the gmail account is ~/.password-store/smtp.gmail.com/jherrlin@gmail.com.gpg. It's the smtpmail-smtp-server and smtpmail-smtp-user vars that points it to that location.

There is a function on message-send-hook that asks to sign or encrypt the message before sending. mml-secure-message-sign-pgpmime and mml-secure-message-encrypt-pgpmime will automatically pick up your gpg keys and add correct data to the message before sending.

#+BEGIN_SRC shell :results output code
   cat ~/.emacs.d/mail.el
#+END_SRC

#+RESULTS:
#+begin_src shell
  (require 'mu4e)
  (require 'org-mu4e)
  (require 'mu4e-contrib)
  (require 'smtpmail)

  (auth-source-pass-enable)
  (setq auth-source-debug t)
  (setq auth-source-do-cache nil)
  (setq auth-sources '(password-store))
  (setq message-kill-buffer-on-exit t)
  (setq message-send-mail-function 'smtpmail-send-it)
  (setq mu4e-attachment-dir "~/Downloads")
  (setq mu4e-change-filenames-when-moving t)
  (setq mu4e-completing-read-function 'completing-read)
  (setq mu4e-compose-complete-addresses t)
  (setq mu4e-compose-context-policy nil)
  (setq mu4e-compose-dont-reply-to-self t)
  (setq mu4e-compose-keep-self-cc nil)
  (setq mu4e-context-policy 'pick-first)
  (setq mu4e-get-mail-command "mbsync -a")
  (setq mu4e-headers-date-format "%d-%m-%Y %H:%M")
  (setq mu4e-headers-fields '((:human-date . 20)
                              (:flags . 6)
                              (:mailing-list . 10)
                              (:from . 22)
                              (:subject)))
  (setq mu4e-headers-include-related t)
  (setq mu4e-sent-messages-behavior 'delete)
  (setq mu4e-view-show-addresses t)
  (setq mu4e-view-show-images t)
  (setq smtpmail-debug-info t)
  (setq smtpmail-stream-type 'starttls)
  (setq mm-sign-option 'guided)

  (when (fboundp 'imagemagick-register-types)
    (imagemagick-register-types))

  (defun sign-or-encrypt-message ()
    (let ((answer (read-from-minibuffer "Sign or encrypt?\nEmpty to do nothing.\n[s/e]: ")))
      (cond
       ((string-equal answer "s") (progn
                                    (message "Signing message.")
                                    (mml-secure-message-sign-pgpmime)))
       ((string-equal answer "e") (progn
                                    (message "Encrypt and signing message.")
                                    (mml-secure-message-encrypt-pgpmime)))
       (t (progn
            (message "Dont signing or encrypting message.")
            nil)))))

  (add-hook 'message-send-hook 'sign-or-encrypt-message)

  (setq mu4e-contexts
        `( ,(make-mu4e-context
             :name "gmail"
             :enter-func (lambda ()
                           (mu4e-message "Entering gmail context")
                           (when (string-match-p (buffer-name (current-buffer)) "mu4e-main")
                             (revert-buffer)))
             :leave-func (lambda ()
                           (mu4e-message "Leaving gmail context")
                           (when (string-match-p (buffer-name (current-buffer)) "mu4e-main")
                             (revert-buffer)))
             :match-func (lambda (msg)
                           (when msg
                             (or (mu4e-message-contact-field-matches msg :to "jherrlin@gmail.com")
                                 (mu4e-message-contact-field-matches msg :from "jherrlin@gmail.com")
                                 (mu4e-message-contact-field-matches msg :cc "jherrlin@gmail.com")
                                 (mu4e-message-contact-field-matches msg :bcc "jherrlin@gmail.com")
                                 (string-match-p "^/gmail/Inbox" (mu4e-message-field msg :maildir)))))
             :vars '( ( user-mail-address            . "jherrlin@gmail.com" )
                      ( smtpmail-smtp-user           . "jherrlin@gmail.com" )
                      ( mu4e-compose-signature       . "Mvh John" )
                      ( smtpmail-smtp-server         . "smtp.gmail.com" )
                      ( smtpmail-smtp-service        . 587 )
                      ( mu4e-maildir-shortcuts       . ((:maildir "/gmail/Inbox" :key ?i)))
                      ( mu4e-bookmarks
                        .
                        (( :name  "Unread messages"
                                   :query "maildir:/gmail/Inbox AND flag:unread AND NOT flag:trashed AND NOT outdoorexperten"
                                   :key ?u)
                          ( :name "Today's messages"
                                  :query "maildir:/gmail/Inbox AND date:today..now"
                                  :key ?t)
                          ( :name "Last 7 days"
                                  :query "maildir:/gmail/Inbox AND date:7d..now"
                                  :hide-unread t
                                  :key ?w)
                          ( :name "Deleted"
                                  :query "flag:trashed"
                                  :key ?d)
                          ( :name "Possibly garbage"
                                  :query "bokio OR outdoorexperten"
                                  :key ?g)))))

           ,(make-mu4e-context
             :name "school"
             :enter-func (lambda ()
                           (mu4e-message "Entering school context")
                           (when (string-match-p (buffer-name (current-buffer)) "mu4e-main")
                             (revert-buffer)))
             :leave-func (lambda ()
                           (mu4e-message "Leaving school context")
                           (when (string-match-p (buffer-name (current-buffer)) "mu4e-main")
                             (revert-buffer)))
             :match-func (lambda (msg)
                           (when msg
                             (or (mu4e-message-contact-field-matches msg :to "jh222jx@student.lnu.se")
                                 (mu4e-message-contact-field-matches msg :from "jh222jx@student.lnu.se")
                                 (mu4e-message-contact-field-matches msg :cc "jh222jx@student.lnu.se")
                                 (mu4e-message-contact-field-matches msg :bcc "jh222jx@student.lnu.se"))))

             :vars '( ( user-mail-address       . "jh222jx@student.lnu.se" )
                      ( smtpmail-smtp-user      . "jh222jx@student.lnu.se" )
                      ( smtpmail-smtp-server    . "smtp.gmail.com" )
                      ( smtpmail-smtp-service   . 587 )
                      ( mu4e-compose-signature  . "Mvh John" )
                      ( mu4e-maildir-shortcuts  . ((:maildir "/lnu/Inbox" :key ?i)))
                      ( mu4e-bookmarks
                        .
                        (( :name  "All school mails"
                                   :query "maildir:/lnu/Inbox"
                                   :key ?a)
                         ( :name  "Unread school messages"
                                   :query "maildir:/lnu/Inbox AND flag:unread AND NOT flag:trashed"
                                   :key ?u)))))))
#+end_src

This is the structure of the smtp credentials file.

#+BEGIN_SRC shell :results output code
  gpg --decrypt ~/.password-store/smtp.gmail.com/jherrlin@gmail.com.gpg
#+END_SRC

#+RESULTS:
#+begin_src shell
<PASSWORD>
user: jherrlin@gmail.com
port: 587
host: smtp.gmail.com
#+end_src

Powered by Hugo & Kiss.