HOME

Mail with Emacs

Maintaining Secrets

GPG Encryption

(require 'epa-file)
(epa-file-enable)

auth-sources

  • Customize auth-sources
  • To enable debug information:

    (setq auth-source-debug t)
    
  • To clear the Emacs authentication cache:

    (auth-source-forget-all-cached)
    
  • The current set of sources:

    auth-sources
    
    • ~/.emacs.d/secrets/.authinfo.gpg
    • macos-keychain-internet

Authentication Secret Look-up

  • Require the auth-sources library

    (require 'auth-source)
    
  • auth-sources usage

    The auth-source-search function takes a “search spec” plist and returns a list with all matching entries. The results themselves are plists containing full entries that matched the search spec.

    (auth-source-search :user "toby.tripp")
    

    The :secret entry will be a lambda that, when evaluated, will return the password.

    (let* ((matches (auth-source-search
                     :user "toby.tripp"
                     :max 1
                     :require '(:secret)))
           (entry (nth 0 matches))
           (secret (plist-get entry :secret)))
      (list
       (list :user   (plist-get entry :user))
       (list :host   (plist-get entry :host))
       (list :port   (plist-get entry :port))
       (list :secret (funcall secret))))
    
    :user toby.tripp
    :host example.server
    :port 80
    :secret incredible-secret

    We will define a function for returning a single auth-sources result as an alist:

    (defun toby/auth-info (&rest search-spec)
      "Given a LOGIN to search for, return `auth-sources' that match.
    
    Valid search keys are:
    
      - :user
      - :host
      - :port
    
    Setting :max will have no result as this function only returns a
    single result.  If there are multiple matches, the first will be
    returned.
    
    Results are returned as an alist with the `:secret' property
    pre-evaluated."
    
      (dolist (default '((:max . 1) (:require . (:secret))))
        (plist-put search-spec (car default) (cdr default)))
    
      (let ((entry (nth 0 (apply 'auth-source-search search-spec))))
        (mapcar (lambda (e)
                  (let ((prop  (car e))
                        (value (if (functionp (cadr e))
                                   (funcall (cadr e))
                                 (cadr e))))
                    (cons prop value)))
                (seq-partition entry 2))))
    

    For example, given an .authinfo file containing:

    machine example.server login toby.tripp port 80 password incredible-secret
    

    and then the following call:

    (toby/auth-info :user "toby.tripp")
    
    ((:host . "example.server") (:user . "toby.tripp") (:port . "80") (:secret . "incredible-secret"))
    
    

    We can then, for example, get just the password like this:

    (alist-get :secret (toby/auth-info :user "toby.tripp"))
    
    "incredible-secret"
    
    

Secret Configurations

Facilities for the evaluation of encrypted elisp configuration.

(defun toby/get-secret-var (sym)
  "Get the value of the symbol `sym' from the custom.el.gpg
  customization/configuration file."
  (with-temp-buffer
    (insert-file-contents "~/.emacs.d/secrets/custom.el.gpg")
    (eval-buffer)
    (alist-get sym toby/secret-custom)))
(toby/get-secret-var (intern property))

ADOPTED mbsync

# -*- mode: conf -*-
Expunge Both
SyncState *

GMail

  • A script for retrieving encrypted authinfo
gpg2 -q --for-your-eyes-only --no-tty -d ~/.emacs.d/secrets/.authinfo.gpg | awk '/imap\.gmail/ { print $8 }'
  • The mbsync configuration for the gmail account
IMAPAccount gmail
Host imap.gmail.com
Port 993
User << get-secret(property="gmail-user") >>
AuthMechs LOGIN
SSLType IMAPS
SystemCertificates no
CertificateFile /usr/local/etc/openssl/cert.pem
PassCmd "<<gmail-password>>"

IMAPStore gmail-remote
Account gmail

Trailing slashes in mail directory entries are important.

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

Patterns match remote mail folders for syncing:

#+tblname pattern-examples

Pattern Meaning
* Match everything
!DIR Exclude DIR
DIR Include DIR

For example:

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

Channel gmail-trash
Master :gmail-remote:"[Gmail]/Trash"
Slave :gmail-local:trash
Create Both
Expunge Both

Channel gmail-sent
Master :gmail-remote:"[Gmail]/Sent Mail"
Slave :gmail-local:sent
Create Both
Expunge Both

Channel gmail-archive
Master ":gmail-remote:[Gmail]/All Mail"
Slave :gmail-local:archive
Create Both

Groups put together Channels, so that we can invoke mbsync on a Group to sync all Channels, for instance: mbsync gmail gets mail from "gmail-inbox", "gmail-sent", and "gmail-trash"

Group gmail
Channel gmail-inbox
Channel gmail-sent
Channel gmail-trash
Channel gmail-archive
mkdir -p ~/.mail/gmail
mbsync -al

iCloud

This configuration uses authentication info stored in the MacOS Keychain. I'm not convinced this is better or worse than authinfo.

IMAPAccount icloud
Host imap.mail.me.com
Port 993
User <<get-secret(property="icloud-user")>>
PassCmd "security find-internet-password -s imap.mail.me.com -w"
AuthMechs LOGIN PLAIN
SSLType IMAPS
SSLVersions TLSv1.2
SystemCertificates no
CertificateFile /usr/local/etc/openssl/cert.pem

IMAPStore icloud-remote
Account icloud
MaildirStore icloud-local
Path ~/.mail/icloud/
Inbox ~/.mail/icloud/Inbox
Trash Trash
Channel icloud-folders
Master :icloud-remote:
Slave :icloud-local:
Patterns "INBOX" "Saved" "Drafts" "Archive" "Sent*" "Trash"
Create Both
Expunge Both
SyncState *
Group icloud
Channel icloud-folders
mkdir -p ~/.mail/icloud
mbsync -l icloud

ADOPTED Wanderlust

  • State "ADOPTED" from "EXPERIMENTAL" [2018-02-23 Fri 10:01]
  • State "EXPERIMENTAL" from [2018-02-09 Fri 21:21]
(require-package 'wanderlust)

Initialization

(require 'mime-setup)
(autoload 'wl       "wl"       "Wanderlust" t)
(autoload 'wl-draft "wl-draft" "Write draft with Wanderlust." t)
(setq
 elmo-maildir-folder-path "~/.mail"
 wl-stay-folder-window t                       ;; show the folder pane (left)
 wl-folder-window-width 25                     ;; toggle on/off with 'i'

 ;; hide many fields from message buffers
 wl-message-ignored-field-list '("^.*:")
 wl-message-visible-field-list
 '("^\\(To\\|Cc\\):"
   "^Subject:"
   "^\\(From\\|Reply-To\\):"
   "^Organization:"
   "^Message-Id:"
   "^Delivered-To:"
   "^\\(Posted\\|Date\\):"
   "^X-Keywords:")
 wl-message-sort-field-list
 '("^From"
   "^Organization:"
   "^X-Attribution:"
   "^Subject"
   "^Date"
   "^To"
   "^Cc"))

ADOPTED Mail indexing with mu

  • State "EXPERIMENTAL" from [2018-02-09 Fri 22:16]
  • Emacs-Fu: Searching emails with wanderlust and mu
  • A sync script for use by the OS Scheduler:

    #!/usr/bin/env bash
    exec &> >(while read line; do echo "$(date -R) $line"; done;)
    
    which -s mbsync
    if [[ $? -ne 0 ]]; then
        brew install isync
    fi
    
    which -s mu
    if [[ $? -ne 0 ]]; then
        brew install mu
    fi
    
    echo Mail Sync &&
        mbsync -Va &&
        echo Mail Index &&
        mu index --maildir=~/.mail --lazy-check
    
  • Under MacOS, this script can be triggered by launchd with the following plist:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
      <dict>
        <key>EnvironmentVariables</key>
        <dict>
          <key>PATH</key>
          <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
        </dict>
        <key>KeepAlive</key>
        <false/>
        <key>Label</key>
        <string>org.thetripps.isync</string>
        <key>ProgramArguments</key>
        <array>
          <string>~/bin/mail-sync</string>
        </array>
        <key>StartInterval</key>
        <integer>300</integer>
        <key>RunAtLoad</key>
        <true />
        <key>StandardOutPath</key>
        <string>/var/log/agents.user/org.thetripps.isync.log</string>
        <key>StandardErrorPath</key>
        <string>/var/log/agents.user/org.thetripps.isync.log</string>
      </dict>
    </plist>
    

ADOPTED Searching Emails

  • State "EXPERIMENTAL" from [2018-02-09 Fri 22:17]

Search mu index by typing g in folder or summary.

(require 'elmo-search)
(elmo-search-register-engine
 'mu 'local-file
 :prog "/usr/local/bin/mu"
 :args '("find" pattern "--fields" "l")
 :charset 'utf-8)

(setq elmo-search-default-engine 'mu)

mu cheat-sheet

Also man mu-find.

ADOPTED org-capture Integration

  • State "EXPERIMENTAL" from [2018-02-10 Sat 07:01]
(global-unset-key (kbd "<f3>"))
(global-set-key (kbd "<f3>") 'wl)

(eval-after-load 'wl
  '(progn
     (require 'org-wl)
     (define-key wl-folder-mode-map (kbd "q") 'bury-buffer)
     (define-key wl-folder-mode-map (kbd "Q") 'wl-exit)
     (fullframe wl bury-buffer nil)))
  • [X] Figure out how to auto-load this package on wanderlust init

With this, you can mark text in a Wanderlust email view and type C-c c e to create an org node with the marked contents. Meta-data about the current email will automatically be included.

Starting Up for Reading

M-x wl

ADOPTED Setting wanderlust as the Emacs Mail composer

  • State "ADOPTED" from "EXPERIMENTAL" [2018-02-28 Wed 20:41]
  • State "EXPERIMENTAL" from [2018-02-10 Sat 22:42]

Open a message for writing with wl-draft

(autoload 'wl-user-agent-compose "wl-draft" nil t)
(if (boundp 'mail-user-agent)
    (setq mail-user-agent 'wl-user-agent))

(if (fboundp 'define-mail-user-agent)
    (define-mail-user-agent
      'wl-user-agent
      'wl-user-agent-compose
      'wl-draft-send
      'wl-draft-kill
      'mail-send-hook))

ADOPTED Try GMail for SMTP

  • State "ADOPTED" from "TODO" [2018-02-28 Wed 20:39]
Incoming Mail (IMAP) Server imap.gmail.com  
  Requires SSL: Yes
  Port: 993
Outgoing Mail (SMTP) Server smtp.gmail.com  
  Requires SSL Yes
  Requires TLS Yes1
  Port for SSL 465
  Port for TLS/STARTTLS 587
(let* ((smtp-server (toby/get-secret-var 'smtp-server))
       (smtp-config (toby/auth-info :host smtp-server)))
  (setq wl-smtp-authenticate-type "plain"
        wl-smtp-posting-server    (alist-get :host smtp-config)
        wl-smtp-posting-port      (alist-get :port smtp-config)
        wl-smtp-posting-user      (alist-get :user smtp-config)
        wl-from           (toby/get-secret-var 'toby/mail-from)
        wl-smtp-connection-type   (quote ssl)))

TODO BBDB for Contacts [0/1]

  • State "ADOPTED" from "EXPERIMENTAL" [2018-03-01 Thu 12:33]
  • State "EXPERIMENTAL" from "TODO" [2018-02-21 Wed 12:46]
  • State "TODO" from [2018-02-21 Wed 12:46]
  • Referenced
  • [ ] BBDB Wanderlust Integration
    • [ ] “BBDB: MUA ‘mime-view-mode’ Not Supported”
      • bbdb-init some change (it's unclear which) fixed this
      • [ ] Turns out that it didn't There is a problem with the hook in wl-message-redisplay-hook, currently set to: bbdb-mua-auto-update. This function appears unable or unwilling to parse the headers from a message viewed with mime-view-mode. BBDB Source Repo.
(require-package 'bbdb (version-to-list "3.2"))
(eval-after-load 'wl
  '(progn
     (require 'bbdb-wl)
     (bbdb-initialize 'wl)
     (bbdb-insinuate-wl)
     (bbdb-mua-auto-update-init 'wl)))

Package Declaration Here

(provide 'mail-in-emacs)

Archive   ARCHIVE

Footnotes:

1

If available

Author: Toby Tripp

Created: 2018-03-04 Sun 00:38

Validate