Add email/{wanderlust,mu4e,notmuch} modules

Removed app/{email,notmuch}
This commit is contained in:
Henrik Lissner 2019-05-05 14:11:59 -04:00
parent 45266213a5
commit 84c5da844b
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
16 changed files with 178 additions and 89 deletions

View file

@ -0,0 +1,91 @@
#+TITLE: email/mu4e
#+DATE: April 8, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Prerequisites][Prerequisites]]
- [[MacOS][MacOS]]
- [[Arch Linux][Arch Linux]]
- [[Features][Features]]
- [[Configuration][Configuration]]
- [[offlineimap][offlineimap]]
- [[mbsync][mbsync]]
* Description
This module makes Emacs an email client, using ~mu4e~.
#+begin_quote
I want to live in Emacs, but as we all know, living is incomplete without email.
So I prayed to the text editor gods and they (I) answered. Emacs+evil's editing
combined with org-mode for writing emails? /Yes please./
It uses ~mu4e~ to read my email, but depends on ~offlineimap~ (to sync my email
via IMAP) and ~mu~ (to index my mail into a format ~mu4e~ can understand).
#+end_quote
** Module Flags
+ ~+gmail~ Enables gmail-specific configuration.
** Plugins
+ [[https://github.com/agpchil/mu4e-maildirs-extension][mu4e-maildirs-extension]]
* Prerequisites
This module requires:
+ Either ~mbsync~ (default) or ~offlineimap~ (to sync mail with)
+ ~mu~ (to index your downloaded messages)
** MacOS
#+BEGIN_SRC sh
brew install mu --with-emacs
# And one of the following
brew install isync # mbsync
brew install offlineimap
#+END_SRC
** Arch Linux
#+BEGIN_SRC sh
sudo pacman --noconfirm --needed -S mu
# And one of the following
sudo pacman -S isync # mbsync
sudo pacman -S offlineimap
#+END_SRC
* TODO Features
* Configuration
** offlineimap
This module uses =mbsync= by default. To change this, change ~+mu4e-backend~:
#+BEGIN_SRC emacs-lisp
(setq +mu4e-backend 'offlineimap)
#+END_SRC
Then you must set up offlineimap and index your mail:
1. Write a ~\~/.offlineimaprc~. Mine can be found [[https://github.com/hlissner/dotfiles/tree/master/shell/mu][in my dotfiles repository]]. It
is configured to download mail to ~\~/.mail~. I use [[https://www.passwordstore.org/][unix pass]] to securely
store my login credentials.
2. Download your email: ~offlineimap -o~ (may take a while)
3. Index it with mu: ~mu index --maildir ~/.mail~
Then configure Emacs to use your email address:
#+BEGIN_SRC emacs-lisp :tangle no
;; Each path is relative to `+mu4e-mu4e-mail-path', which is ~/.mail by default
(set-email-account! "Lissner.net"
'((mu4e-sent-folder . "/Lissner.net/Sent Mail")
(mu4e-drafts-folder . "/Lissner.net/Drafts")
(mu4e-trash-folder . "/Lissner.net/Trash")
(mu4e-refile-folder . "/Lissner.net/All Mail")
(smtpmail-smtp-user . "henrik@lissner.net")
(user-mail-address . "henrik@lissner.net")
(mu4e-compose-signature . "---\nHenrik Lissner"))
t)
#+END_SRC
** TODO mbsync

View file

@ -0,0 +1,78 @@
;;; email/mu4e/autoload/email.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-email-account! (label letvars &optional default-p)
"Registers an email address for mu4e. The LABEL is a string. LETVARS are a
list of cons cells (VARIABLE . VALUE) -- you may want to modify:
+ `user-full-name' (this or the global `user-full-name' is required)
+ `user-mail-address' (required)
+ `smtpmail-smtp-user' (required for sending mail from Emacs)
OPTIONAL:
+ `mu4e-sent-folder'
+ `mu4e-drafts-folder'
+ `mu4e-trash-folder'
+ `mu4e-refile-folder'
+ `mu4e-compose-signature'
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
default/fallback account."
(after! mu4e
(when-let* ((address (cdr (assq 'user-mail-address letvars))))
(add-to-list 'mu4e-user-mail-address-list address))
(setq mu4e-contexts
(cl-loop for context in mu4e-contexts
unless (string= (mu4e-context-name context) label)
collect context))
(let ((context (make-mu4e-context
:name label
:enter-func (lambda () (mu4e-message "Switched to %s" label))
:leave-func #'mu4e-clear-caches
:match-func
(lambda (msg)
(when msg
(string-prefix-p (format "/%s" label)
(mu4e-message-field msg :maildir))))
:vars letvars)))
(push context mu4e-contexts)
(when default-p
(setq-default mu4e-context-current context))
context)))
(defvar +mu4e-workspace-name "*mu4e*"
"TODO")
(add-hook 'mu4e-main-mode-hook #'+mu4e|init)
;;;###autoload
(defun =mu4e ()
"Start email client."
(interactive)
(require 'mu4e)
(+workspace-switch +mu4e-workspace-name t)
(mu4e~start 'mu4e~main-view)
;; (save-selected-window
;; (prolusion-mail-show))
)
;;;###autoload
(defun +mu4e/compose ()
"Compose a new email."
(interactive)
;; TODO Interactively select email account
(call-interactively #'mu4e-compose-new))
;;
;; Hooks
(defun +mu4e|init ()
(add-hook 'kill-buffer-hook #'+mu4e|kill-mu4e nil t))
(defun +mu4e|kill-mu4e ()
;; (prolusion-mail-hide)
(when (+workspace-exists-p +mu4e-workspace-name)
(+workspace/delete +mu4e-workspace-name)))

View file

@ -0,0 +1,21 @@
;; email/mu4e/autoload/evil.el -*- lexical-binding: t; -*-
;;;###if (featurep! :editor evil)
;;;###autoload
(defun +mu4e/mark (&optional beg end)
"Mark all messages within the current selection in mu4e's header view. Uses
`this-command-keys' to see what flag you mean."
(interactive)
(let* ((beg (or beg (and (region-active-p) evil-visual-beginning) (line-beginning-position)))
(end (or end (and (region-active-p) evil-visual-end) (line-end-position)))
(key (this-command-keys))
(command
(car (cl-find-if (lambda (mark) (equal (car (plist-get (cdr mark) :char)) key))
mu4e-marks))))
(unless command
(error "Not a valid mark command: %s" key))
(when (bound-and-true-p evil-mode)
(evil-normal-state))
(goto-char beg)
(dotimes (_ (count-lines beg end))
(mu4e-headers-mark-and-next command))))

View file

@ -0,0 +1,178 @@
;;; email/mu4e/config.el -*- lexical-binding: t; -*-
(defvar +mu4e-backend 'mbsync
"Which backend to use. Can either be offlineimap, mbsync or nil (manual).")
;;
;;; Packages
(add-to-list 'auto-mode-alist '("\\.\\(?:offlineimap\\|mbsync\\)rc\\'" . conf-mode))
(def-package! mu4e
:commands (mu4e mu4e-compose-new)
:init
(provide 'html2text) ; disable obsolete package
(setq mu4e-maildir "~/.mail"
mu4e-attachment-dir "~/.mail/.attachments"
mu4e-user-mail-address-list nil)
:config
(pcase +mu4e-backend
(`mbsync
(setq mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t))
(`offlineimap
(setq mu4e-get-mail-command "offlineimap -o -q")))
(setq mu4e-update-interval nil
mu4e-compose-format-flowed t ; visual-line-mode + auto-fill upon sending
mu4e-view-show-addresses t
mu4e-sent-messages-behavior 'sent
mu4e-hide-index-messages t
;; try to show images
mu4e-view-show-images t
mu4e-view-image-max-width 800
;; configuration for sending mail
message-send-mail-function #'smtpmail-send-it
smtpmail-stream-type 'starttls
message-kill-buffer-on-exit t ; close after sending
;; start with the first (default) context;
mu4e-context-policy 'pick-first
;; compose with the current context, or ask
mu4e-compose-context-policy 'ask-if-none
;; use helm/ivy
mu4e-completing-read-function
(cond ((featurep! :completion ivy) #'ivy-completing-read)
((featurep! :completion helm) #'completing-read)
(t #'ido-completing-read))
;; no need to ask
mu4e-confirm-quit nil
;; remove 'lists' column
mu4e-headers-fields
'((:account . 12)
(:human-date . 12)
(:flags . 4)
(:from . 25)
(:subject)))
;; set mail user agent
(setq mail-user-agent 'mu4e-user-agent)
;; Use fancy icons
(setq mu4e-headers-has-child-prefix '("+" . "")
mu4e-headers-empty-parent-prefix '("-" . "")
mu4e-headers-first-child-prefix '("\\" . "")
mu4e-headers-duplicate-prefix '("=" . "")
mu4e-headers-default-prefix '("|" . "")
mu4e-headers-draft-mark '("D" . "")
mu4e-headers-flagged-mark '("F" . "")
mu4e-headers-new-mark '("N" . "")
mu4e-headers-passed-mark '("P" . "")
mu4e-headers-replied-mark '("R" . "")
mu4e-headers-seen-mark '("S" . "")
mu4e-headers-trashed-mark '("T" . "")
mu4e-headers-attach-mark '("a" . "")
mu4e-headers-encrypted-mark '("x" . "")
mu4e-headers-signed-mark '("s" . "")
mu4e-headers-unread-mark '("u" . ""))
;; Add a column to display what email account the email belongs to.
(add-to-list 'mu4e-header-info-custom
'(:account
:name "Account"
:shortname "Account"
:help "Which account this email belongs to"
:function
(lambda (msg)
(let ((maildir (mu4e-message-field msg :maildir)))
(format "%s" (substring maildir 1 (string-match-p "/" maildir 1)))))))
;; Refresh the current view after marks are executed
(defun +mu4e*refresh (&rest _) (mu4e-headers-rerun-search))
(advice-add #'mu4e-mark-execute-all :after #'+mu4e*refresh)
(when (featurep! :tools flyspell)
(add-hook 'mu4e-compose-mode-hook #'flyspell-mode))
;; Wrap text in messages
(setq-hook! 'mu4e-view-mode-hook truncate-lines nil)
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
(set-evil-initial-state!
'(mu4e-main-mode
mu4e-view-mode
mu4e-headers-mode
mu4e-compose-mode
mu4e~update-mail-mode)
'normal))
(def-package! mu4e-maildirs-extension
:after mu4e
:config
(mu4e-maildirs-extension)
(setq mu4e-maildirs-extension-title nil))
(def-package! org-mu4e
:hook (mu4e-compose-mode . org-mu4e-compose-org-mode)
:config
(setq org-mu4e-link-query-in-headers-mode nil
org-mu4e-convert-to-html t)
;; Only render to html once. If the first send fails for whatever reason,
;; org-mu4e would do so each time you try again.
(add-hook! 'message-send-hook
(setq-local org-mu4e-convert-to-html nil)))
;;
;;; Gmail integration
(when (featurep! +gmail)
(after! mu4e
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
(setq mu4e-sent-messages-behavior 'delete
;; don't need to run cleanup after indexing for gmail
mu4e-index-cleanup nil
;; because gmail uses labels as folders we can use lazy check since
;; messages don't really "move"
mu4e-index-lazy-check t)
;; In my workflow, emails won't be moved at all. Only their flags/labels are
;; changed. Se we redefine the trash and refile marks not to do any moving.
;; However, the real magic happens in `+mu4e|gmail-fix-flags'.
;;
;; Gmail will handle the rest.
(defun +mu4e--mark-seen (docid _msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))
(delq (assq 'delete mu4e-marks) mu4e-marks)
(setf (alist-get 'trash mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action #'+mu4e--mark-seen)
;; Refile will be my "archive" function.
(alist-get 'refile mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action #'+mu4e--mark-seen))
;; This hook correctly modifies gmail flags on emails when they are marked.
;; Without it, refiling (archiving), trashing, and flagging (starring) email
;; won't properly result in the corresponding gmail action, since the marks
;; are ineffectual otherwise.
(defun +mu4e|gmail-fix-flags (mark msg)
(pcase mark
(`trash (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
(`refile (mu4e-action-retag-message msg "-\\Inbox"))
(`flag (mu4e-action-retag-message msg "+\\Starred"))
(`unflag (mu4e-action-retag-message msg "-\\Starred"))))
(add-hook 'mu4e-mark-execute-pre-hook #'+mu4e|gmail-fix-flags)))

View file

@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; email/mu4e/packages.el
(package! mu4e-maildirs-extension)