Managing Email from Emacs. Surely that's crazy-talk, but hey… let's give it a try.
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.
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
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
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.
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
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.
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
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:
(setq mu4e-get-mail-command "offlineimap")
(setq mu4e-view-show-addresses t)
(setq mu4e-attachment-dir (expand-file-name "~/Downloads/"))
(setq mu4e-maildir "path/to/Mail")
(setq mu4e-html2text-command "w3m -T text/html") ;; alternatively "textutil -stdin -format html -convert txt -stdout"
(setq mu4e-user-mail-address-list '("myself@domain1.com"
"myself@domain2.com"))
(setq mu4e-context-policy 'pick-first)
(setq 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 . "My name")
(mu4e-sent-folder . "/Domain1/Sent")
(mu4e-drafts-folder . "/Domain1/Drafts")
(mu4e-trash-folder . "/Domain1/Trash")
(mu4e-compose-signature . nil)
(mu4e-compose-format-flowed . nil)
(smtpmail-smtp-user . "myself@domain1.com")
(smtpmail-smtp-server . "smtp.domain1.com")
(smtpmail-smtp-service . 587)))
(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 . "My name")
(mu4e-sent-folder . "/Domain2/Sent")
(mu4e-drafts-folder . "/Domain2/Drafts")
(mu4e-trash-folder . "/Domain2/Trash")
(mu4e-compose-signature . nil)
(mu4e-compose-format-flowed . nil)
(smtpmail-smtp-user . "myself@domain2.com")
(smtpmail-smtp-server . "smtp.domain2.com")
(smtpmail-smtp-service . 587))))))
(use-package smtpmail
:config
(setq smtpmail-stream-type 'starttls)
(setq smtpmail-debug-info t)
(setq smtpmail-warn-about-unknown-extensions t)
(setq smtpmail-queue-mail t)
(setq smtpmail-default-smtp-server nil)
;; Created with mu mkdir path/to/Mail/queue
;; Also avoid indexing.
;; touch path/to/Mail/queue/.noindex
(setq smtpmail-queue-dir "path/to/Mail/queue/cur"))
(use-package message
:config
(setq message-send-mail-function 'smtpmail-send-it))
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.