Emails with EMACS

Table of Contents

1 Summary

This document explains a setup to read emails within EMACS, with offline and synchronized mailboxes.

2 Migration process

  • I've been using Thunderbird to manage my emails for quite many years. It was for me the best "mainstream" Linux alternative among many other applications I've pretty much all tried…
  • I was never fully satisfied though, mostly because of the excruciatingly slow and tedious search, lack of integration with a nice agenda/to-do organizer, and lack of integration with the filesystem. I wanted my emails, filesystem, and agenda to communicate altogether in order to manage projects.
  • I was also never fully satisfied with the way my emails were organized (i.e., in folders). I kept creating new folders for sub/sub/sub projects, making searches even more complicated.
  • After migrating most of my workflow to EMACS (agenda with org-mode, calendar, text editing, programming, file manager…), it was clear that a good solution would be emails within EMACS.
  • There are several possibilities for this and the setup can be quite intimidating and overwhelming, so I took a few days just browsing feedback, checking pros and cons before making a final choice. Alternatives I considered were offlineimap for synchronizing and gnus, wanderlust, and notmuch for the email management.
  • Final setup chosen:
    • mbsync to synchronize (push/pull).
    • mu to index emails.
    • mu4e as the EMACS layer to mu and as an email manager.

3 What is an email (or why I stopped caring about folders)?

  • Emails are indeed not a to-do list, they shouldn't be kept visible/important/flagged/etc… as long as an action is not taken on them. The action must be decided when reading the email, and if there is an associated task, it belongs in the organizer! Therefore, either we remove, just read, read and reply, or read and associate a task (which can be "don't forget to reply to" or a work task).
  • Emails are just a way to communicate, and can be used to build an external, relatively independent, to-do list. They are also a (bad) way to share files, but that doesn't have to be this way (download the file & remove the attachment!).
  • Although mbsync and mu4e can deal with folders and labels (à la Gmail) very well (you just need to include them or the entire mailbox in the mbsync settings), mu4e is so good at searching (either live or with preset filters, aka "bookmarks") that folders are not that useful anymore, at least not for folders as a way to organize emails and find them quickly. Accounts/folders/labels, in my opinion, should be used as a way to manage very different scopes (e.g., work/private), and mu4e is definitely well adapted for this.
  • The point of no return is to remove all labels, thereby flattening out the mailbox. After a few weeks I realized it's really ok to have all the emails in a single inbox (actually I kept one label because I forward my work emails to Gmail and also keep this Gmail account for personal emails….). The point is that if you define an action for each email you receive, then you can forget about them. If you do need to look for specific emails, mu is more than capable. The workflow is within the organizer file (e.g., org-mode), not in the mailbox.

4 What this setup enables

  • Fast search queries.
  • Multiple contexts with various addresses and servers that can be switched instantly, e.g.:
    • @work: use IMAP and SMTP servers from work
    • @homework: use home SMTP, set reply-to to work address etc…
    • @gmail: use gmail IMAP and SMTP, reply-to at gmail etc…
  • mu4e is well integrated within EMACS and can communicate, for instance, with org-mode. It's possible to capture to-do items from an email and get a link, then click the link to open the email again.
  • mu4e does auto-completion of email addresses by default. It can write and read rich emails or HTML emails. Can also view emails in browser if email is really filled with images and HTML, remove attachment, and plenty of other things (see mu4e section)…

5 mbsync

5.1 Steps

  • Install mbsync (Ubuntu: sudo apt-get install isync).
  • setup password with gpg2.
  • edit .mbsyncrc file (other file names can be chose but that's the default one).

5.2 Confifuration file .mbsynrc

IMAPAccount gmail
# Address to connect to
Host imap.gmail.com
User myname@gmail.com
# Pass passwordinclearsobadidea
# To store the password in an encrypted file use PassCmd instead of Pass
PassCmd "gpg2 -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine imap.gmail.com login myname@gmail.com/ {print $NF}'"

# Port 993
# Use SSL
AuthMechs Login
SSLType IMAPS
#SSLVersions SSLv3
# Try one of the following
CertificateFile /etc/ssl/certs/ca-certificates.crt
#CertificateFile /opt/local/share/curl/curl-ca-bundle.crt
#CertificateFile ~/.cert/imap.gmail.com.pem
#CertificateFile ~/.cert/Equifax_Secure_CA.pem
#CertificateFile /etc/ssl/certs/ca-bundle.crt

#------------------------------------------------------------------

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
# The trailing "/" is important
Path ~/.mail/gmail/
Inbox ~/.mail/gmail/Inbox

#------------------------------------------------------------------

Channel gmail-inbox
Master :gmail-remote:
Slave :gmail-local:
Patterns "INBOX"
Create Both
Expunge Both
SyncState *

#can choose a different name for local
Channel gmail-mylabel1
Master :gmail-remote:"label1"
Slave :gmail-local:"Local_label1"
Create Both
Expunge Both
SyncState *

#better to remove space in local name
Channel gmail-drafts
Master :gmail-remote:"[Gmail]/Sent Mail"
Slave :gmail-local:"[Gmail].SentMail"
Create Both
Expunge Both
SyncState *

Channel gmail-drafts
Master :gmail-remote:"[Gmail]/Drafts"
Slave :gmail-local:"[Gmail].Drafts"
Create Both
Expunge Both
SyncState *

#...

Group gmail
Channel gmail-inbox
Channel gmail-mylabel2
Channel gmail-drafts

# can repeat IMAPAccount
  • Quick remarks pertaining to Gmail:
    • IMAP must be enabled (through the Gmail web interface)
    • IMAP folder names are "regionalized" (i.e., they have different names depending on which language you choose, again through the web interface).
    • It seems that spaces in local (slave) folders can create some problems, e.g., with commands ran through mu4e (particularly relevant for "Sent Mail").
  • It is useful to do a manual synchronization in verbose mode in the terminal first (i.e., not in mu4e) to make sure everything is in order (when comparing number of messages sync'ed, remember that your mailbox, e.g., Gmail, may return conversations and not individual messages). Synchronization may be done in the following ways:
    • sync one channel: mbsync -V gmail-inbox
    • sync one group: mbsync -V gmail
    • sync all: mbsync -aV
    • sync, specifying the file: mbsync -c ~.mbsyncrc/
  • If no password is provided (no Pass or PassCmd), it will be prompted in the terminal. This makes initial tests easy to make sure mbsync is working fine. The password cannot be prompted in the mu4e interface, so it will have to be provided through an encrypted file (see following).
  • Mail index can also be tested manually with mu: mu index –maildir=~.mail/. If a message appears that the database needs update, do mu index -rebuild.
  • If moving cur/new/tmp folders around between maildirs, and error message "Maildir error: UID 2 is beyond highest assigned UID 1", use rename 's,U=[1-9][0-9]*.*//' */*/ in the maildir where the files have been moved.

5.3 gpg2 password management

5.3.1 Solution 1

  • First, create a temporary file (e.g., ~/tmp) with your password inside.
  • Make sure ~.gnupg/ is not owned by root.
  • Then (–: two dashes): gpg2 –output .mbsyncpass.gpg –symmetric tmp and provide passphrase.
  • Remove temporary file!
  • Use in .mbsynrc the following: /PassCmd "gpg2 -q –for-your-eyes-only –no-tty -d .mbsyncpass.gpg"/

5.3.2 Solution 2

  • First create a temporary file (e.g., ~/tmp) with:
machine imap.gmail.com login myname@gmail.com password MYPASSWORD
machine smtp.gmail.com login myname@gmail.com password MYPASSWORD
  • Then (–: two dashes): gpg2 –output .authinfo.gpg –symmetric tmp
  • Remove temporary file!
  • Use in .mbsynrc the following PassCmd "gpg2 -q –for-your-eyes-only –no-tty -d .authinfo.gpg | awk '/machine imap.gmail.com login myname@gmail.com {print $NF}'"/
  • Normally the SMTP line can be used within emacs to provide the SMTP settings and password.

6 mu4e

  • Now we want to install mu4e (Ubuntu: sudo apt-get install mu4e).
  • Put all the necessary customization in .emacs, including SMTP server (see configuration file below).
  • Quick note: mu4e buffers have a leading space (e.g., /" mu4e-main"/) and they don't appear in the buffer list.
  • It is useful to install w3 or html2text to convert HTML to text and altermime to remove attachment from emails. You need EMACS with ImageMagick support to display images.

6.1 Configuration in .emacs

  • Here are some cool settings that are not enabled by default:
    • Don't include myself when replying to all.
    • Alerts to know when emails comes (which I don't use; I like to check messages when I decide).
  • Here are some useful additional capabilities enabled in the configuration file:
    • Compose in new frame (window).
    • Warn if there should be an attachment.
    • Always highlight some particular words in messages.
    • Quick search for all messages from sender.
    • Ask for location of URL fetched files.
    • Automatically create non-existing directories (incl. parent) when saving attachment or URL fetched file.
    • Use either filename or directory as input to save attachment.
    • Save all attachments to any directory with no confirmation.
    • Remove attachments from message.
  • The following .emacs configuration is a patchwork of many posts I found online. My apologies to people who found the solutions and whose names I didn't keep…
;;====================== MAIL =============================

;; MU4E
(require 'mu4e)

;; emacs allows you to select an e-mail program as the default program it uses when you press C-x m (compose-mail), call report-emacs-bug and so on. If you want to use mu4e for this, you do so by adding the following to your configuration:
(setq mail-user-agent 'mu4e-user-agent)

;; don't keep message buffers around
(setq message-kill-buffer-on-exit t)

;; no need to confirm
(setq mu4e-confirm-quit nil)

;; display is definitely nicer with these
(setq mu4e-use-fancy-chars t)

;; open mail links in new frame
;;(defun mu4e-link-in-new-frame (MSGID) (select-frame (make-frame)))
;;(advice-add 'mu4e-view-message-with-message-id :before 'mu4e-link-in-new-frame)

;; single window mode (not useful to me, I like to have the split windows)
;;(setq mu4e-split-view 'single-window) 

;;mu4e buffers have a leading space...
;;" *mu4e-main"

;;------------------ servers

;; get mail
(setq mu4e-get-mail-command "/usr/bin/mbsync -c ~/.mbsyncrc gmail" ;;use U in main view to update
      mu4e-html2text-command "html2text -utf8 -width 72" ;;seems to be doing a better job than w3m
      ;;w3m-command "/usr/bin/w3m"
      ;;w3m -dump -T text/html -cols 72 -o display_link_number=true -o auto_image=false -o display_image=false -o ignore_null_img_alt=true"
      mu4e-update-interval 300 ;;every 5 minutes
      mu4e-headers-auto-update t
      mu4e-view-prefer-html t
      )
;;You can then get your e-mail using M-x mu4e-update-mail-and-index, or C-S-u in all mu4e-views; alternatively, you can use C-c C-u, which may be more convenient if you use emacs in a terminal.
;;You can kill the (foreground) update process with q.

;; faster indexing, if that becomes a problem
;(setq
;  mu4e-index-cleanup nil      ;; don't do a full cleanup check
;  mu4e-index-lazy-check t)    ;; don't consider up-to-date dirs



;;------------------ folders, search

;; defaults
(setq mu4e-maildir (expand-file-name "~/.mail"))
;; these are modified in the contexts
(setq mu4e-trash-folder nil ;; must be configured later by context
      mu4e-drafts-folder nil ;; must be configured later by context
      mu4e-sent-folder nil ;; must be configured later by context
      mu4e-compose-reply-to-address nil ;; must be configured later by context
      mu4e-compose-signature nil ;; must be configured later by context
      )

;; don't save message to Sent Messages, IMAP takes care of this
(setq mu4e-sent-messages-behavior 'delete)

;; shortcuts
;; you can quickly switch to your Inbox -- press ``ji''
;; then, when you want archive some messages, move them to
;; the 'All Mail' folder by pressing ``ma''.
(setq mu4e-maildir-shortcuts
      '( ("/gmail/Inbox"                    . ?i)
         ("/gmail/[Gmail].SentMail"  . ?s)
         ("/gmail/[Gmail].Trash"      . ?t)
         ("/gmail/[Gmail].Drafts"     . ?d)))


;;rename files when moving
;;NEEDED FOR MBSYNC
(setq mu4e-change-filenames-when-moving t)

;; unbind default bookmarks
(makunbound 'mu4e-bookmarks)
(defvar mu4e-bookmarks
  `( ,(make-mu4e-bookmark
       :name  "Unread @CEA"
       :query "maildir:/gmail/Inbox AND flag:unread"
       :key ?u)))
;; bookmarks
(add-to-list 'mu4e-bookmarks
  (make-mu4e-bookmark
    :name  "Today @CEA"
    :query "maildir:/gmail/Inbox AND date:today..now"
    :key ?t)  )
(add-to-list 'mu4e-bookmarks
  (make-mu4e-bookmark
    :name  "Last 7 days @CEA"
    :query "maildir:/gmail/Inbox AND date:7d..now"
    :key ?w)  )
(add-to-list 'mu4e-bookmarks
  (make-mu4e-bookmark
    :name  "w/ attachment @CEA"
    :query "maildir:/gmail/Inbox AND flag:attach"
    :key ?a)  )
(add-to-list 'mu4e-bookmarks
  (make-mu4e-bookmark
    :name  "Big messages @CEA"
    :query "maildir:/gmail/Inbox AND size:5M..500M"
    :key ?b)  )



;;------------------ addresses


;; general emacs mail settings; used when composing e-mail
;; the non-mu4e-* stuff is inherited from emacs/message-mode
;; later defined in contexts
(setq user-mail-address nil
    user-full-name nil)

;; To determine whether a message was sent by you, mu4e uses the variable mu4e-user-mail-address-list, a list of your e-mail addresses.
(setq mu4e-user-mail-address-list '("xxx@xxx.xxx" "xxx2@xxx.xxx"))


;;------------------ compose

;; list of contacts
;;in shell: mu cfind

;; allows reading other emails while composing
(setq mu4e-compose-in-new-frame t)

;; please don't ever include me when I reply...
(setq mu4e-compose-dont-reply-to-self t)
'
;; signature
(setq message-signature-file "/signature.txt") ; put your signature in this file
;;mu4e-compose-signature-auto-include nil

;; spell check
(add-hook 'mu4e-compose-mode-hook
        (defun my-do-compose-stuff ()
           "My settings for message composition."
           (set-fill-column 72)
           (flyspell-mode)))

;; SMTP
(require 'smtpmail)

(setq message-send-mail-function 'smtpmail-send-it
      smtpmail-debug-info t)

;; following, when set to t, only consider addresses that were seen in personal messages — that is, messages in which one of my e-mail addresses (mu4e-user-mail-address-list) was seen in one of the address fields. This is to exclude mailing list posts.
;;(setq mu4e-compose-complete-only-personal t) ;;somehow removes everything... maybe wait till full sync is done?

;; warn if no attachments
(defun message-attachment-present-p ()
  "Return t if an attachment is found in the current message."
  (save-excursion
    (save-restriction
      (widen)
      (goto-char (point-min))
      (when (search-forward "<#part" nil t) t))))
(defcustom message-attachment-intent-re
  (regexp-opt '("attach"
		"attached"
		"fichier joint"
		"PDF"
		"attachment"))
  "A regex which - if found in the message, and if there is no
attachment - should launch the no-attachment warning.")
(defcustom message-attachment-reminder
  "Are you sure you want to send this message without any attachment? "
  "The default question asked when trying to send a message
containing `message-attachment-intent-re' without an
actual attachment.")
(defun message-warn-if-no-attachments ()
  "Ask the user if s?he wants to send the message even though
there are no attachments."
  (when (and (save-excursion
	       (save-restriction
		 (widen)
		 (goto-char (point-min))
		 (re-search-forward message-attachment-intent-re nil t)))
	     (not (message-attachment-present-p)))
    (unless (y-or-n-p message-attachment-reminder)
      (keyboard-quit))))
;; add hook to message-send-hook (so also works with gnus)
(add-hook 'message-send-hook #'message-warn-if-no-attachments)

;; It is possible to attach files to mu4e messages (using dired in the same frame process...)
;; mark the file(s) in dired you would like to attach and press C-c RET C-a, and you’ll be asked whether to attach them to an existing message, or create a new one.
(require 'gnus-dired)
;; make the `gnus-dired-mail-buffers' function also work on
;; message-mode derived modes, such as mu4e-compose-mode
(defun gnus-dired-mail-buffers ()
  "Return a list of active message buffers."
  (let (buffers)
    (save-current-buffer
      (dolist (buffer (buffer-list t))
        (set-buffer buffer)
        (when (and (derived-mode-p 'message-mode)
                (null message-sent-message-via))
          (push (buffer-name buffer) buffers))))
    (nreverse buffers)))
(setq gnus-dired-mail-mode 'mu4e-user-agent)
(add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode)

;; highlight some words
;; http://emacs-fu.blogspot.co.uk/2008/12/highlighting-todo-fixme-and-friends.html
(set-face-attribute 'font-lock-warning-face nil :foreground "red" :weight 'bold :background "yellow")
(add-hook 'mu4e-compose-mode-hook
          (defun mu4e-highlight-words ()
            "Flag attachment keywords"
            (font-lock-add-keywords nil
                                    '(("\\(urgent\\|myname\\|reply\\)" 1 font-lock-warning-face t)))))

;; remove automatic wrapping of lines
(add-hook 'mu4e-compose-mode-hook 'turn-off-auto-fill)


;;------------------ headers

;; the headers to show in the headers list -- a pair of a field
;; and its width, with `nil' meaning 'unlimited'
;; (better only use that for the last field.
;; These are the defaults:
(setq mu4e-headers-fields
    '( (:human-date          .  11)    ;; date, human-date
       (:flags         .   6)
       (:from          .  22) ;; from , to, from-or-to
       (:to            .  22)
       ;(:mailing-list . 10)
       (:thread-subject       .  nil))) ;; subject, thread-subject

;; date format
 (setq mu4e-headers-date-format "%d-%m-%Y")
 (setq mu4e-headers-time-format "[%H:%M]")

;;thread options
(setq
      ;; thread prefix marks
      mu4e-headers-has-child-prefix '("."  . "◼ ")
      mu4e-headers-default-prefix '(" "  . "│ ")
      )

;; show full addresses in view message (instead of just names)
;; toggle per name with M-RET
;; (setq mu4e-view-show-addresses 't)


;;------------------ view

;; show images
(setq mu4e-view-show-images t
       mu4e-show-images t
       mu4e-view-image-max-width 800
       mu4e-image-max-width 800)
;; use imagemagick, if available
(when (fboundp 'imagemagick-register-types)
  (imagemagick-register-types))

;; show addresses
(setq mu4e-view-show-addresses t) 

;; ---- actions

;; add view in browser for html messages
(add-to-list 'mu4e-view-actions
  '("ViewInBrowser" . mu4e-action-view-in-browser) t)

;; search for sender
(defun search-for-sender (msg)
  "Search for messages sent by the sender of the message at point."
  (mu4e-headers-search
   (concat "from:" (cdar (mu4e-message-field msg :from)))))
;; define 'x' as the shortcut
(add-to-list 'mu4e-view-actions
	     '("xsearch for sender" . search-for-sender) t)


;; overwrite save attachment to propose making non-existing directories first
;; also allows using directory as input
(defun mu4e-view-save-attachment-single (&optional msg attnum)
  "Save attachment number ATTNUM from MSG.
If MSG is nil use the message returned by `message-at-point'.
If ATTNUM is nil ask for the attachment number."
  (interactive)
  (let* ((msg (or msg (mu4e-message-at-point)))
	  (attnum (or attnum
		    (mu4e~view-get-attach-num "Attachment to save" msg)))
	  (att (mu4e~view-get-attach msg attnum))
	  (fname  (plist-get att :name))
	  (mtype  (plist-get att :mime-type))
	  (path (concat
		 (mu4e~get-attachment-dir fname mtype) "/"))
	  (index (plist-get att :index))
	  (retry t) (fpath))
    ;;path is attachment dir (~)
    (while retry
      (setq fpath (mu4e~view-request-attachment-path fname path))
      (setq path2 (file-name-directory fpath))
      (setq retry
	    (and (not (file-exists-p path2))
		 (not (y-or-n-p (mu4e-format "Create directory '%s'?" path2))))))
    (make-directory path2 t)
    ;;if input is a directory, append fname
    (if (file-directory-p (substitute-in-file-name fpath))
	(setq fpath (concat fpath "/" fname)))
    ;;should we overwrite?
    (while retry
      (setq retry
	    (and (file-exists-p fpath)
		 (not (y-or-n-p (mu4e-format "Overwrite '%s'?" fpath))))))
    (mu4e~proc-extract
     'save (mu4e-message-field msg :docid)
     index mu4e-decryption-policy fpath)))


;; overwrite to save all attachments with no confirmation
(defun mu4e-view-save-attachment-multi2 (&optional msg)
  "Offer to save multiple email attachments from the current message.
Default is to save all messages, [1..n], where n is the number of
attachments.  You can type multiple values separated by space, e.g.
  1 3-6 8
will save attachments 1,3,4,5,6 and 8.
Furthermore, there is a shortcut \"a\" which so means all
attachments, but as this is the default, you may not need it."
  (interactive)
  (let* ((msg (or msg (mu4e-message-at-point)))
	 (count (hash-table-count mu4e~view-attach-map))
	 (attachnums (mu4e-split-ranges-to-numbers "a" count)))
    (let* ((path (concat (mu4e~get-attachment-dir) "/"))
	   (attachdir (mu4e~view-request-attachments-dir path)))
      (dolist (num attachnums)
	(let* ((att (mu4e~view-get-attach msg num))
	       (fname  (plist-get att :name))
	       (index (plist-get att :index))
	       (retry t)
	       fpath)
	  (while retry
	    (setq fpath (expand-file-name (concat attachdir fname) path))
	    (setq retry
		  (and (file-exists-p fpath)
		       (not (y-or-n-p
			     (mu4e-format "Overwrite '%s'?" fpath))))))
	  (mu4e~proc-extract
	   'save (mu4e-message-field msg :docid)
	   index mu4e-decryption-policy fpath))))
    ))
(define-key mu4e-view-mode-map (kbd "s") 'mu4e-view-save-attachment-multi2)


;; overwrite fetch URL to propose making non-existing directories first and choosing the filename
(defun mu4e-view-fetch-url (&optional multi)
  "Offer to fetch (download) urls(s). If MULTI (prefix-argument) is nil,
download a single one, otherwise, offer to fetch a range of
URLs. The urls are fetched to `mu4e-attachment-dir'."
  (interactive "P")
  (mu4e~view-handle-urls "URL to fetch" multi
			 (lambda (url)
			   (setq retry t)
			   (while retry
			     (setq fname (file-name-nondirectory url))
			     (setq path (concat
				    (mu4e~get-attachment-dir fname) "/"))
			     (setq fpath (mu4e~view-request-attachment-path fname path))
			     (setq path2 (file-name-directory fpath))
			     (setq retry
				   (and (not (file-exists-p path2))
					(not (y-or-n-p (mu4e-format "Create directory '%s'?" path2))))))
			   (make-directory path2 t)
			   (while retry
			     (setq retry
				   (and (file-exists-p fpath)
					(not (y-or-n-p (mu4e-format "Overwrite '%s'?" fpath))))))
			   (url-copy-file url fpath)
			   (mu4e-message "Fetched %s -> %s" url fpath))
    ))



;; remove all attachments from email. This works also for Gmail, with the tweak that the message is moved around so as to force an update on the IMAP server
;; This adds a new "attachment action" in Mu4e. When viewing an email, press A, then R to select the action defined above, then enter the number of the attachment. The action will ask you for confirmation before deleting the attachment. The header listing the attachments is not updates but when you leave the email and reopen it, the attachment is gone.
;;also check mu4e-detach
(defun my-remove-attachment (msg num) 
  "Remove attachment."

  (let* ((x (mu4e~view-construct-attachments-header msg));; Populate attachment map
	 (count (hash-table-count mu4e~view-attach-map));; Count the attachments
	 (attachnums (mu4e-split-ranges-to-numbers "a" count));; A list of numbers for all attachments
	 )
    (let (fnamelist '())
    ;; Save all attachments
    (dolist (num attachnums)
      (let* ((att (mu4e~view-get-attach msg num))
	     (fname (plist-get att :name)))
	(add-to-list 'fnamelist fname)))
  
  (let* ((path (mu4e-msg-field msg :path))
	 (disc-txt (format "Removed attachments:\n- %s\n"
		  (s-join "\n- " fnamelist)))
	 (disc-txt-fpath "/tmp/disclaimer.txt")
         (cmd (format "altermime --input=%s --removeall --disclaimer='%s'" path disc-txt-fpath)))
    (with-temp-file disc-txt-fpath (insert disc-txt))
    (shell-command cmd)
    (message cmd))

  ))
  ;move mail around so it gets updated in IMAP
  (let* ((oldmaildir (mu4e-message-field msg :maildir)))
      (mu4e~proc-move (mu4e-message-field msg :message-id) "/gmailvl/[Gmail].Drafts/")
      (mu4e~proc-move (mu4e-message-field msg :message-id) oldmaildir))
  )
(add-to-list 'mu4e-view-attachment-actions
             '("remove-attachments" . my-remove-attachment))


;; add action to show the local filename of the message
(defun my-show-filename (msg)
  (kill-new (mu4e-message-field msg :path))
    )
(add-to-list 'mu4e-view-actions
             '("filename in kill-ring" . my-show-filename))
(add-to-list 'mu4e-headers-actions
             '("filename in kill-ring" . my-show-filename))



;;------------------ multiple accounts/contexts

;; Multiple accounts: https://www.djcbsoftware.nl/code/mu/mu4e/Multiple-accounts.html
;; You can put any variable you want in the account lists, just make sure that you put in all the variables that differ for each account. Variables that do not differ need not be included. For example, if you use the same SMTP server for both accounts, you don’t need to include the SMTP-related variables in my-mu4e-account-alist.

;; match-func can automatically switch context
(setq mu4e-contexts
      `( ,(make-mu4e-context
	   :name "CEA"
	   :enter-func (lambda () (mu4e-message "CEA context"))
	   ;;:match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "personal-email@gmail.com")))
	   :leave-func (lambda () (setq mu4e-maildir-list nil))
	   :vars '((user-mail-address . "xxx@xxx.xxx")
		   (mu4e-reply-to-address . "xxx@xxx.xxx")
		   (mu4e-compose-reply-to-address . "xxx@xxx.xxx")
		   (mu4e-sent-folder .  "/gmail/[Gmail].SentMail")
		   (mu4e-drafts-folder . "/gmail/[Gmail].Drafts")
		   (mu4e-trash-folder . "/gmail/[Gmail].Trash")
		   ;;mu4e-refile-folder "/archive")   ;; saved messages
		   (smtpmail-default-smtp-server . "mx.xxx.xxx")
		   (smtpmail-local-domain . "xxx.xxx")
		   (smtpmail-smtp-user . "login")
		   (smtpmail-smtp-server "mx.xxx.xxx")
		   (smtpmail-stream-type . starttls)
		   (smtpmail-smtp-service . 25))
	   )
	 ,(make-mu4e-context
	   :name "homework"
	   :enter-func (lambda () (mu4e-message "home-work context"))
	   ;;:match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "work-email@gmail.com")))
	   :leave-func (lambda () (setq mu4e-maildir-list nil))
	   :vars '((user-mail-address . "xxx@xxx.xxx")
		   (mu4e-reply-to-address . "xxx@xxx.xxx")
		   (mu4e-compose-reply-to-address . "xxx@xxx.xxx")
		   (smtpmail-default-smtp-server . "smtp.googlemail.com")
		   (smtpmail-local-domain . "googlemail.com")
		   (smtpmail-smtp-user . "myname")
		   (smtpmail-smtp-server . "smtp.googlemail.com")
		   (smtpmail-stream-type . starttls)
		   (smtpmail-smtp-service . 587)) 
	   )
	 ,(make-mu4e-context
	   :name "gmail"
	   :enter-func (lambda () (mu4e-message "gmail context"))
	   ;;:match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "work-email@gmail.com")))
	   :leave-func (lambda () (setq mu4e-maildir-list nil))
	   :vars '((user-mail-address . "myname@gmail.com")
		   (mu4e-reply-to-address . "myname@gmail.com")
		   (mu4e-compose-reply-to-address . "myname@gmail.com")
		   (smtpmail-default-smtp-server . "smtp.googlemail.com")
		   (smtpmail-local-domain . "googlemail.com")
		   (smtpmail-smtp-user . "myname")
		   (smtpmail-smtp-server . "smtp.googlemail.com")
		   (smtpmail-stream-type . starttls)
		   (smtpmail-smtp-service . 587)) 
	   )
	 )
      )

;; compose with the current context if no context matches;
(setq mu4e-compose-context-policy nil)

;;------------------ trash

;;somehow trash flag don't seem to work for gmail, it keeps showing
;;in all-mail. Just move them to Trash, gmail seems to take care of the
;;trashed flag just fine
(add-to-list 'mu4e-marks
  '(archive
     :char       ("D" . "❌❌")
     :prompt     "Delete"
     ;:show-target (lambda (target) "trash!")
     :dyn-target (lambda (target msg) (mu4e-get-trash-folder msg))
     :action      (lambda (docid msg target)
                    ;; must come before proc-move since retag runs
                    ;; 'sed' on the file
                    (mu4e~proc-move docid (mu4e~mark-check-target target)))))
(mu4e~headers-defun-mark-for archive)
(define-key mu4e-headers-mode-map (kbd "D") 'mu4e-headers-mark-for-archive)
;;also overwrite other existing marks
(add-to-list 'mu4e-marks
  '(archive
     :char       ("d" . "❌❌")
     :prompt     "delete"
     ;:show-target (lambda (target) "trash!")
     :dyn-target (lambda (target msg) (mu4e-get-trash-folder msg))
     :action      (lambda (docid msg target)
                    ;; must come before proc-move since retag runs
                    ;; 'sed' on the file
                    (mu4e~proc-move docid (mu4e~mark-check-target target)))))
(mu4e~headers-defun-mark-for archive)
(define-key mu4e-headers-mode-map (kbd "d") 'mu4e-headers-mark-for-archive)


;;------------------ help

;;help
(defun show-mu4e-main-help ()
  (interactive)
  (message-box "
MU4E
----
?:?
"))

(defun show-mu4e-view-help ()
  (interactive)
  (message-box "
MU4E-view
---------

R,F,C: reply, forward, compose
a: message actions
A: attachment actions

g: visit URLs
f: fetch URLs
e: save (extract) attachment
s: save all attachments

C-cr: capture reply to
C-ct: capture todo-mail
"))

(defun show-mu4e-headers-help ()
  (interactive)
  (message-box "
MU4E-headers
------------

P:\t threading on/off
R,F,C: reply, forward, compose
E:\t edit (drafts)

d/=:  trash/untrash
DEL,D: deletion
m:\t move
r:\t refile
u/U: unmark (all) message(s)
t,T: mark (sub)thread
Flags: +/- importance
       ?,!: unread/read
x:\t execute actions

/,M-arrows: narrow-down and navigate queries

C-cr: capture reply to
C-ct: capture todo-mail
"))

(defun show-mu4e-compose-help ()
  (interactive)
  (message-box "
MU4E-compose
------------
C-c C-a: attach
C-c C-c: send
C-c C-k: cancel
C-x C-s: save in drafts

TAB: cycle through email addresses suggestions

C-ct: capture todo-mail
"))

;;shortcut
(global-set-key (read-kbd-macro "<C-f2>") 'mu4e)



(defun mu4e-new-frame ()
  (interactive)
  (mu4e)
  (select-frame (make-frame))
  )
(global-set-key (read-kbd-macro "<S-f2>") 'mu4e-new-frame)

;; specific shortcuts for calfw
(progn
  ;; keyboard shortcuts
  (require 'mu4e)
  (define-key mu4e-main-mode-map (kbd "<C-f1>") 'show-mu4e-help)
  (define-key mu4e-view-mode-map (kbd "<C-f1>") 'show-mu4e-view-help)
  (define-key mu4e-headers-mode-map (kbd "<C-f1>") 'show-mu4e-headers-help)
  (define-key mu4e-compose-mode-map (kbd "<C-f1>") 'show-mu4e-compose-help)
  (define-key mu4e-view-mode-map (kbd "\C-cr") 'org-capture-replymail)
  (define-key mu4e-headers-mode-map (kbd "\C-cr") 'org-capture-replymail)
  (define-key mu4e-view-mode-map (kbd "\C-ct") 'org-capture-todomail)
  (define-key mu4e-headers-mode-map (kbd "\C-ct") 'org-capture-todomail)
  (define-key mu4e-compose-mode-map (kbd "\C-ct") 'org-capture-todomail)
  )




;;====================== ORG CAPTURE =============================
;;https://orgmode.org/manual/Template-elements.html#Template-elements


;;store org-mode links to messages
(require 'org-mu4e)
;;store link to message if in header view, not to header query. When you are in Headers view, M-x org-store-link links to the query if org-mu4e-link-query-in-headers-mode is non-nil, and to the particular message otherwise (which is the default).
(setq org-mu4e-link-query-in-headers-mode nil)

(setq org-capture-templates
      '(("t" "Todo-mail" entry (file+headline "~/workflow.org" "Captured todo-mails")
         "\n\n* TODO %:subject %?\nSCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"+0d\"))\n%a\n")
	("r" "Reply to" entry (file+headline "~/workflow.org" "Reply to")
         "\n\n* TODO Reply to %:fromname on %:subject %?\nSCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"+0d\"))\n%a\n")
	("b" "Bookmark" entry (file+headline "~/bookmarks.org" "Captured")
	 "\n\n* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n"))
      )
;; https://orgmode.org/manual/Template-expansion.html

;; function to capture a bookmark
(defun org-capture-bookmark ()
  (interactive)
  "Capture a bookmark item"
  (org-capture nil "b"))

;; function to capture a mail todo
(defun org-capture-todomail ()
  (interactive)
  "Capture a todo-mail item"
  (org-capture nil "t"))

;; function to capture a mail todo
(defun org-capture-replymail ()
  (interactive)
  "Capture a todo-mail item"
  (org-capture nil "r"))

;; bind
(define-key global-map "\C-cc" 'org-capture)

Author: Vianney Lebouteiller

Created: 2018-05-25 Fri 18:32

Emacs 25.2.2 (Org mode 8.2.10)

Validate