Handling email with Emacs' Wanderlust
After some months using Thunderbird, I got a bit tired of some intrinsic problems with the speed of email synchronization, or the simple fact that saving an email to a folder would constantly fail. I have finally reverted back to handling email in Emacs, which is extremely productive, one you jump the hassle of configuring it.
My interface of choice is Wanderlust. There are some general guides for configuring it scattered through the web, plus a page on Emacs' wiki. In practice you will face some problems in the following fields:
- Connection configuration: server, ports, type of connection, secrets, etc.
- Handling and caching of emails.
- Displaying and sending html emails.
- Attachments
- Contacts database.
I have not solved all of them, but my configuration is good enough that I think it is worth sharing. Here is the piece of code that I have in my Emacs' init file.
(use-package wl ;; The name of the package in MELPA is "wanderlust" but the ;; feature provided by that package is 'wl :ensure wanderlust :commands (wl) :bind (:map wl-summary-mode-map ("b a" . (lambda () (interactive) (djcb-wl-summary-refile "%Archives.2019"))) ;;Swap a and A in summary mode, so citing original message is on a and no-cite on A. ("A" . wl-summary-reply) ("a" . wl-summary-reply-with-citation) ("r b" . jjgr-bbdb-mua-auto-update) ) :hook ((wl-mail-send-pre . djcb-wl-draft-subject-check) (wl-mail-send-pre . djcb-wl-draft-attachment-check) (wl-mail-setup . wl-draft-config-exec)) :config (progn (print "Wanderlust configured") (defun jjgr-bbdb-mua-auto-update () (interactive) (wl-summary-enter-handler) (bbdb-mua-auto-update nil 'query) (mime-preview-quit)) (defun djcb-wl-summary-refile (&optional folder) "refile the current message to FOLDER; if FOLDER is nil, use the default" (interactive) (wl-summary-refile (wl-summary-message-number) folder) (wl-summary-exec)) ;; Store sent emails in the current folder (defun jjgr-determine-fcc-buffer () (if (or (equal wl-message-buffer-cur-folder "%INBOX") (null wl-message-buffer-cur-folder)) "%Sent" wl-message-buffer-cur-folder)) (setq wl-fcc 'jjgr-determine-fcc-buffer) ;; Check messages for missing subject or abstract (defun djcb-wl-draft-subject-check () "check whether the message has a subject before sending" (if (and (< (length (std11-field-body "Subject")) 1) (null (y-or-n-p "No subject! Send current draft?"))) (error "Abort."))) (defun djcb-wl-draft-attachment-check () "if attachment is mention but none included, warn the the user" (save-excursion (goto-char 0) (unless ;; don't we have an attachment? (re-search-forward "^Content-Disposition: attachment" nil t) (when ;; no attachment; did we mention an attachment? (or (re-search-forward "attach" nil t) (re-search-forward "adjunt" nil t)) (unless (y-or-n-p "Possibly missing an attachment. Send current draft?") (error "Abort.")))))) (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)) (require 'bbdb) ) ; progn :init (setq wl-folders-file "~/OnlineFolder/Library/dot.folders" ;; SMTP server for mail posting. wl-smtp-posting-server "smtp.some.server.org" wl-smtp-posting-port 587 wl-smtp-posting-user "user-name" wl-smtp-authenticate-type "plain" wl-smtp-connection-type 'starttls wl-from "some.email@some.server.org" ;; Do not cache passwords. The cache corrupts server ;; secrets. password-cache nil elmo-imap4-default-user "user-name" elmo-imap4-default-server "imap.some.server.org" elmo-imap4-default-port 993 elmo-imap4-default-authenticate-type 'clear elmo-imap4-default-stream-type 'ssl elmo-passwd-storage-type 'auth-source ;; Location of archives elmo-archive-folder-path "~/OnlineFolder/Mail/" ;; Location of MH and Maildir folders elmo-localdir-folder-path "~/OnlineFolder/Mail/" elmo-maildir-folder-path "~/OnlineFolder/Mail/" wl-message-id-domain "some.email@some.server.org" wl-from "Some person <some.email@some.server.org>" mime-edit-default-signature "~/OnlineFolder/Library/dot.signature" wl-forward-subject-prefix "Fwd: " wl-default-folder "%INBOX" ;; my main inbox wl-biff-check-folder-list '("%INBOX") ;; check every 180 seconds wl-biff-check-interval 180 wl-draft-folder "%Drafts" ;; store drafts in 'postponed' wl-trash-folder "%Trash" ;; put trash in 'trash' wl-stay-folder-window t wl-folder-window-width 25 wl-folder-use-frame nil wl-message-ignored-field-list '("^.*") wl-message-visible-field-list '("^From:" "^To:" "^Cc:" "^Date:" "^Subject:") wl-message-sort-field-list wl-message-visible-field-list wl-summary-width 120 ;; No width wl-summary-default-sort-spec 'date wl-message-window-size '(1 . 2) ;; Always download emails without confirmation wl-prefetch-threshold nil wl-message-buffer-prefetch-threshold nil elmo-message-fetch-threshold nil ;; Rendering of messages using 'shr', Emacs' simple html ;; renderer, but without fancy coloring that distorts the ;; looks mime-view-text/html-previewer 'shr shr-use-colors nil wl-draft-config-alist '(((string-match "1" "1") (bottom . "\n--\n") (bottom-file . "~/OnlineFolder/Library/dot.signature")) ) ;; don't ****ing split large messages mime-edit-split-message nil ) ) ; use-package wanderlust
I keep these files in some online storage that I share between computers, which makes synchronizing databases easier. In ~/OnlineFolder/Library/ I create a dot.folders file with the folders that I am interested in from my IMAP server.
The final %/ is there to allow me to browse all other folders in the server, leaving them hidden by default.
The next step is to set up the contacts database. I import regularly contacts from Google, because they are kept up-to-date by the mobile phone. However, that requires some tweaking of standard libraries. Another goodie is the use of bbdb to auto-complete addresses when writing the header of an email, just by pressing <tab>. Finally, I have deactivated BBDB3's automatic gathering of contacts. The Wanderlust configuration invokes BBDB to do this when I press the key <r> followed by <b> in the email summary.
(use-package bbdb :ensure t :commands (bbdb-initialize) :hook ((mail-setup . bbdb-mail-aliases) (message-setup . bbdb-mail-aliases) (wl-mail-setup . jjgr-add-bbdb-tab-completion)) :init (setq bbdb-file "~/OnlineFolder/Library/Emacs/bbdb" bbdb-mua-pop-up t bbdb-mua-pop-up-window-size t) :config (progn (bbdb-initialize 'wl) (bbdb-mua-auto-update-init 'wl) (defun my-bbdb-complete-mail () "If on a header field, calls `bbdb-complete-mail' to complete the name." (interactive) (when (< (point) (save-excursion (goto-char (point-min)) (search-forward (concat "\n" mail-header-separator "\n") nil 0) (point))) (bbdb-complete-mail))) (defun jjgr-add-bbdb-tab-completion () (define-key (current-local-map) (kbd "<tab>") 'my-bbdb-complete-mail)) ) ; progn ) ; use-package bbdb (use-package bbdb-csv-import :ensure t :defer t :commands (bbdb-csv-import-file bbdb-csv-import-buffer) :after (bbdb) :config ;; Remove fields that Google Contacts creates, inserting them at the right ;; position. ;; 1. additional-name -> prepend it to lastname (defun jjgr-fix-bbdb-csv-import () (interactive) (dolist (r (bbdb-records)) (print r) (let* ((fields (bbdb-record-xfields r)) (aname (assoc 'additional-name fields))) (when aname (print aname) (let ((new-lastname (concatenate 'string (cdr aname) " " (bbdb-record-lastname r)))) ;;(setf (bbdb-record-lastname r) new-lastname) (bbdb-record-set-lastname r new-lastname) (bbdb-record-set-xfields r (assoc-delete-all 'additional-name fields)) ))))) )