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 Configuration 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-mylabel2
Channel gmail-drafts
Channel gmail-inbox

# 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 (this will rename all files in cur and new to remove everything after ,U=)

5.3 Troubleshooting

  • If a timeout occurs (Socket error on imap.gmail.com: timeout), try removing hidden files in corresponding maildir/folder (e.g., Inbox), and synchronize again manually with, e.g., mbsync -V gmail-inbox. Also try removing entire channel and resychronize

6 gpg2 password management

6.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 (–: two dashes): PassCmd "gpg2 -q –for-your-eyes-only –no-tty -d .mbsyncpass.gpg"

6.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.3 Troubleshooting

  • If mail retrieval hangs for other reasons than mailbox connection issues, make sure that gpg-agent is not stuck (e.g., by trying to view the authinfo file manually) and if necessary, kill the corresponding job.

7 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.

7.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…

7.2 code in .emacs

  1. General configuration
    ;; 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)
    
    ;; single window mode
    ;(setq mu4e-split-view 'single-window) 
    
    ;;mu4e buffers have a leading space...
    ;;" *mu4e-main"
    
    ;; display is definitely nicer with these
    (setq mu4e-use-fancy-chars t)
    
    
  2. Addresses
    ;; general emacs mail settings; used when composing e-mail
    ;; the non-mu4e-* stuff is inherited from emacs/message-mode
    ;; later redefined in contexts
    (setq user-mail-address "xxx@xxx" ;;cannot be nil for export to HTML (org)
        user-full-name "myname") ;;cannot be nil for export to HTML (org)
    
    ;; 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" "yyy@yyy"))
    
    
  3. Getting mail
    ;; get mail
    (setq mu4e-get-mail-command "/usr/bin/mbsync -a" ;-a ~/.mbsyncrc" ;;-c ~/.mbsyncrc gmail" ;;use U in main view to update
    ;;(setq mu4e-get-mail-command "/usr/bin/offlineimap -o" ;-a ~/.mbsyncrc" ;;-c ~/.mbsyncrc gmail" ;;use U in main view to update
          ;;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"
          ;;w3m-command "/usr/bin/w3m"
          mu4e-update-interval 3600 ;;every hour...
          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
    ;(setq
    ;  mu4e-index-cleanup nil      ;; don't do a full cleanup check
    ;  mu4e-index-lazy-check t)    ;; don't consider up-to-date dirs
    
    
  4. Folders, bookmarks, search
    ;; defaults
    (setq mu4e-maildir (expand-file-name "~/.mail"))
    ;;(setq mu4e-maildir (expand-file-name "/media/DATA/Mail"))
    ;; /!!!\ ln -s "/media/DATA/Mail" .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
          )
    ;(setq mu4e-sent-folder   "/gmail/[Gmail].SentMail")
    ;(setq mu4e-drafts-folder "/gmail/[Gmail].Drafts")
    ;(setq mu4e-trash-folder  "/gmail/[Gmail].Trash")
    
    ;; 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/GmailOnly"                . ?G)
             ("/gmail/[Gmail].SentMail"  . ?S)
             ("/gmail/[Gmail].Trash"      . ?T)
             ("/gmail/[Gmail].Drafts"     . ?D)
             ("/gmailvl/Inbox"                    . ?i)
             ("/gmailvl/[Gmail].SentMail"  . ?s)
             ("/gmailvl/[Gmail].Trash"      . ?t)
             ("/gmailvl/[Gmail].Drafts"     . ?d)
             ))
    
    
    ;;j-i   jump to `inbox
    ;;j-s   jump to `sent
    ;;? mark as unread
    ;;! mark as read
    ;;D delete, d trash
    ;;u remove mark, U remove all marks
    ;;x execute
    ;;R reply
    ;;C-c C-c send / C-c C-k abort
    
    
    ;;rename files when moving
    ;;NEEDED FOR MBSYNC
    (setq mu4e-change-filenames-when-moving t)
    
    ;; unbind default bookmarks
    (makunbound 'mu4e-bookmarks)
    ;(fmakunbound 'mu4e-bookmarks)
    (defvar mu4e-bookmarks
      `( ,(make-mu4e-bookmark
           :name  "Unread @CEA"
           :query "maildir:/gmail/Inbox AND flag:unread"
           :key ?u)))
    
    ;; CEA bookmarks
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Today @CEA"
        :query "maildir:/gmailvl/Inbox AND date:today..now"
        :key ?t)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Last 7 days @CEA"
        :query "maildir:/gmailvl/Inbox AND date:7d..now"
        :key ?w)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "w/ attachment @CEA"
        :query "maildir:/gmailvl/Inbox AND flag:attach"
        :key ?a)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Big messages @CEA"
        :query "maildir:/gmailvl/Inbox AND size:5M..500M"
        :key ?b)  )
    
    ;; Gmail
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Unread @Gmail"
        :query "maildir:/gmail/Inbox AND flag:unread"
        :key ?U)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Today @Gmail"
        :query "maildir:/gmail/Inbox AND date:today..now"
        :key ?T)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Last 7 days @Gmail"
        :query "maildir:/gmail/Inbox AND date:7d..now"
        :key ?W)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "w/ attachment @Gmail"
        :query "maildir:/gmail/Inbox AND flag:attach"
        :key ?A)  )
    (add-to-list 'mu4e-bookmarks
      (make-mu4e-bookmark
        :name  "Big messages @Gmail"
        :query "maildir:/gmail/Inbox AND size:5M..500M"
        :key ?B)  )
    
    
    
    ;; Get all messages regarding bananas:
    ;;          bananas
    
    ;; Get all messages regarding bananas from John with an attachment:
    ;;          from:john and flag:attach and bananas
    
    ;; Get all messages with subject wombat in June 2017
    ;;          subject:wombat and date:20170601..20170630
    
    ;; Get all messages with PDF attachments in the /projects folder
    ;;          maildir:/projects and mime:application/pdf
    
    ;; Get all messages about Rupert in the /Sent Items folder. Note that maildirs with spaces must be quoted.
    ;;          "maildir:/Sent Items" and rupert
    
    ;; Get all important messages which are signed:
    ;;          flag:signed and prio:high
    
    ;; Get all messages from Jim without an attachment:
    ;;          from:jim and not flag:attach
    
    ;; Get all messages with Alice in one of the contacts-fields (to, from, cc, bcc):
    ;;          contact:alice
    
    ;; Get all unread messages where the subject mentions Ångström: (search is case-insensitive and accent-insensitive, so this matches Ångström, angstrom, aNGstrøM, ...)
    ;;          subject:Ångström and flag:unread
    
    ;; Get all unread messages between Mar-2012 and Aug-2013 about some bird:
    ;;          date:20120301..20130831 and nightingale and flag:unread
    
    ;; Get today’s messages:
    ;;          date:today..now
    
    ;; Get all messages we got in the last two weeks regarding emacs:
    ;;          date:2w.. and emacs
    
    ;; Get messages from the Mu mailing list:
    ;;          list:mu-discuss.googlegroups.com
    ;; Note — in the Headers view you may see the ‘friendly name’ for a list; however, when searching you need the real name. You can see the real name for a mailing list from the friendly name’s tool-tip.
    
    ;; Get messages with a subject soccer, Socrates, society, ...; note that the ‘*’-wildcard can only appear as a term’s rightmost character:
    ;;          subject:soc*
    
    ;; Get all messages not sent to a mailing-list:
    ;;          NOT flag:list
    
    ;; Get all mails with attachments with filenames starting with pic; note that the ‘*’ wildcard can only appear as the term’s rightmost character:
    ;;          file:pic*
    
    ;; Get all messages with PDF-attachments:
    ;;          mime:application/pdf
    
    ;; Get all messages with image attachments, and note that the ‘*’ wildcard can only appear as the term’s rightmost character:
    ;;          mime:image/*
    
    
    
  5. 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 "~/ownCloud/org/signature.txt") ; put your signature in this file
    
    ;;mu4e-compose-signature-auto-include nil
    
    ;; don't save message to Sent Messages, IMAP takes care of this
    (setq mu4e-sent-messages-behavior 'delete) ;set in contexts
    
    
    ;; 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)
    
    ;; eventually make one for CEA IMAP
    (setq message-send-mail-function 'smtpmail-send-it
          smtpmail-debug-info t)
    
    ;; 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"
                    "joint"
                    "joins"
                    "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\\|vianney\\|attach\\)" 1 font-lock-warning-face t)))))
    
    ;; remove automatic wrapping of lines
    (add-hook 'mu4e-compose-mode-hook 'turn-off-auto-fill)
    
    
  6. Headers
    ;; default sorting
    ;; (setq mu4e-headers-sort-direction 'ascending)
    
    ;; 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
    
    ;; The letters in the ‘Flags’ field correspond to the following: D=draft, F=flagged (i.e., ‘starred’), N=new, P=passed (i.e., forwarded), R=replied, S=seen, T=trashed, a=has-attachment, x=encrypted, s=signed, u=unread. The tooltip for this field also contains this information.
    
    ;; 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)
    
    ;; mark all visible messages as read
    ;;('mu4e-headers-flag-all-read)
    
    ;;When in the headers view, which displays your email messages, you can easily navigate through different messages using n and p and hitting return will open a message, allowing you to read it. In addition, mu4e includes some very useful marking capabilities: d marks a message for deletion, r for refiling/archiving, and m for moving (after a target directory is specified). Simply press x to "execute" the marks. In addition, with * you can "bulk mark" emails; pressing x after some messages have been marked with x will allow you to perform an action to all of them. See the mu4e user manual for more details.
    
    
  7. 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) 
    
    ;;(setq mu4e-msg2pdf "/path/to/msg2pdf")
    
    ;; view HTML messages
    ;;mu4e-html2text-command "w3m -dump -T text/html" ;;"w3m -dump -T text/html"
    ;;mu4e-html2text-command "html2text -utf8 -width 72" ;;seems to be doing a better job than w3m
    (require 'mu4e-contrib)
    ;;(setq mu4e-html2text-command 'mu4e-shr2text)
    (setq shr-color-visible-luminance-min 50)
    (setq shr-color-visible-distance-min 5)
    (setq mu4e-html2text-command-orig "html2text -utf8 -width 72")
    (setq mu4e-html2text-command mu4e-html2text-command-orig)
    
    ;; toggle between html2text and mu4e-shr2text
    (defun mu4e-view-toggle-html2 ()
      "Toggle html-display of the message body (if any)."
      (interactive)
      (setq mu4e-html2text-command (if (string= mu4e-html2text-command mu4e-html2text-command-orig) 'mu4e-shr2text mu4e-html2text-command-orig))
      (setq mu4e~view-html-text 'html)
      (mu4e-view-refresh)
    )
    (define-key mu4e-view-mode-map    (kbd "C-c h") 'mu4e-view-toggle-html2)
    
    
    ;(defun mu4e-view-toggle-html ()
    ;  "Toggle html-display of the message body (if any)."
    ;  (interactive)
    ;  (setq mu4e~view-html-text
    ;    (if mu4e~message-body-html 'text 'html))
    ;(mu4e-view-refresh))
    
    ;; 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)
    
    
  8. 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))
        ))
    
    
    ;(defun try-move ()
    ;  (interactive)
    ;  (mu4e~proc-move (mu4e-message-field-at-point :message-id) "/gmailvl/[Gmail].Drafts" nil)
    ;  )
    ;(add-to-list 'mu4e-headers-actions
    ;             '("dd" . try-move))
    ;;(defun try-move (msg)
    ;;  (mu4e~proc-move (mu4e-message-field msg :message-id) "/gmailvl/[Gmail].Drafts")
    ;;  )
    ;;(add-to-list 'mu4e-headers-actions
    ;;             '("dd" . try-move))
    
    ;; 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))
    
    ;; remove attachment?
    ;;https://emacs.stackexchange.com/questions/23812/remove-attachments-from-emails
    ;; 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.
    ;(defun my-remove-attachment2 (msg num) 
    ;  "Remove attachment." 
    ;  (let* ((attach (mu4e~view-get-attach msg num))
    ;         (path (mu4e-msg-field msg :path))
    ;         (filename (and attach (plist-get attach :name)))
    ;         (cmd (format "altermime --input=%s --remove='%s'"  path filename)))
    ;    (when (and filename
    ;               (yes-or-no-p
    ;                (format "Are you sure you want to remove '%s'?" filename)))
    ;      (shell-command cmd)
    ;      (message cmd))))
    ;(add-to-list 'mu4e-view-attachment-actions
    ;             '("remove-attachment2" . my-remove-attachment2))
    
    
    ;(defun my-remove-attachment (msg num) 
    ;  "Remove attachment." 
    ;  (let* ((path (mu4e-msg-field msg :path))
    ;        ;; Create the disclaimer files
    ;        (disc-txt "Removed attachments")
    ;        (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))
     ; (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-to-list 'mu4e-headers-actions
    ;             '("remove-attachments" . my-remove-attachment))
    
    
    ;; 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) 
      "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
             (num 1)
             )
        (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-headers-actions
                 '("remove-attachments" . my-remove-attachment))
    
    (defun my-remove-attachment2 (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-attachment2))
    
    
    
    
    
    ;(defun speak-message (msg)
    ;  (mu4e-action-message-to-speech msg))
    ;;; define 's' as the shortcut
    ;(add-to-list 'mu4e-view-actions
    ;            '("sspeech" . speak-message) t)
    
  9. Flags, marking
    ;;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 nil "+S-u-N") ;; mark as read first
                        (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 nil "+S-u-N") ;; mark as read first
                        (mu4e~proc-move docid (mu4e~mark-check-target target))
                        (mu4e~proc-move docid nil "+S-u-N") ;; mark as read 
    )))
    (mu4e~headers-defun-mark-for archive)
    (define-key mu4e-headers-mode-map (kbd "d") 'mu4e-headers-mark-for-archive)
    
    
  10. 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.
    
    ;; in the following, leave-func is because when switching contexts with errors, autocompletion of addresses doesn't work anymore... https://github.com/djcb/mu/issues/1016
    
    ;add?
    ;(setq mu4e-sent-folder   "/gmail/[Gmail].SentMail")
    ;(setq mu4e-drafts-folder "/gmail/[Gmail].Drafts")
    ;(setq mu4e-trash-folder  "/gmail/[Gmail].Trash")
    
    ;;context policies
    (setq mu4e-context-policy 'pick-first) ;;useful to pick first automatically because context is chosen automatically afterwards anyway
    ;;     mu4e-compose-context-policy 'ask)
    
    ;(setq message-send-mail-function 'smtpmail-send-it
    ;      starttls-use-gnutls t
    ;      smtpmail-debug-info t
    ;      )
    
    (defun checkk (&optional msg attnum)
      (interactive)
      (setq msg (mu4e-message-at-point))
      (message-box (format "%s" (string= (mu4e-message-field msg :maildir) "/gmailvl/Inbox")))
      (message-box (format "%s" (string-match "gmailvl/" (mu4e-message-field msg :maildir))))
      )
    
    
    ;; 
    (if at-cea
        (setq mu4e-contexts `( ,(make-mu4e-context
               :name "CEA"
               :enter-func (lambda () (mu4e-message "CEA context")
                             (setq mu4e-sent-messages-behavior 'sent)) ;; make a copy in sent-folder defined below, which is different than the CEA SMTP/IMAP sent folder
               ;;:match-func (lambda (msg) (and 'check-if-at-CEA (or (when msg (mu4e-message-contact-field-matches msg :to "[[:ascii:]]+@cea.fr")) (when msg (mu4e-message-contact-field-matches msg :to "yyy@yyy")) )))
               :match-func (lambda (msg) (and at-cea (when msg (string-match "gmailvl/" (mu4e-message-field msg :maildir)))))
               :leave-func (lambda () (setq mu4e-maildir-list nil))
               :vars '((user-mail-address . "xxx@xxx")
                       (user-full-name . "xxx")
                       (mu4e-reply-to-address . "xxx@xxx")
                       (mu4e-compose-reply-to-address . "xxx@xxx")
                       (mu4e-compose-signature . t)
                       ;;(mu4e-sent-messages-behavior . (quote sent))
                       (mu4e-sent-folder .  "/gmailvl/[Gmail].SentMail")
                       (mu4e-drafts-folder . "/gmailvl/[Gmail].Drafts")
                       (mu4e-trash-folder . "/gmailvl/[Gmail].Trash")
                       ;;mu4e-refile-folder "/archive")   ;; saved messages
                       (smtpmail-default-smtp-server . "xxx")
                       (smtpmail-local-domain . "xxx")
                       (smtpmail-smtp-user . "xxx")
                       (smtpmail-smtp-server . "xxx")
                       (smtpmail-stream-type . plain)
                       (smtpmail-smtp-service . 25))
               )
             ;;smtp.gmail.com port 587/TLS 465/SSL Normal password
             ,(make-mu4e-context
               :name "xxx (@CEA)"
               :enter-func (lambda () (mu4e-message "xxx @CEA context")
                             (setq mu4e-sent-messages-behavior 'sent)) ;; make a copy in sent-folder defined below, which is different than the CEA SMTP/IMAP sent folder
               ;;:match-func (lambda (msg) (and 'check-if-at-CEA (when msg (mu4e-message-contact-field-matches msg :to "yyy"))))
               :match-func (lambda (msg) (and at-cea (when msg (string-match "gmail/" (mu4e-message-field msg :maildir)))))
               :leave-func (lambda () (setq mu4e-maildir-list nil))
               :vars '((user-mail-address . "yyy@yyy")
                       (user-full-name . "yyy")
                       (mu4e-reply-to-address . "yyy@yyy")
                       (mu4e-compose-reply-to-address . "yyy@yyy")
                       (mu4e-compose-signature . nil)
                       ;;(mu4e-sent-messages-behavior . (quote delete))
                       (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 . "xxx")
                       (smtpmail-local-domain . "xxx")
                       (smtpmail-smtp-user . "xxx")
                       (smtpmail-smtp-server . "xxx")
                       (smtpmail-stream-type . plain)
                       (smtpmail-smtp-service . 25))
               ))
    
    
    ;; compose with the current context if no context matches;
    (setq mu4e-compose-context-policy nil)
    
    ;(defun mu4e-auto-context (at-cea)
    ;(if at-cea
    ;    (mu4e-context-switch nil "CEA")
    ;    (mu4e-context-switch nil "homework")
    ;    )
    ;)
    
    
    
    
  11. mu4e-alert
    ;;https://devhub.io/repos/iqbalansari-mu4e-alert
    ;;(mu4e-alert-set-default-style 'libnotify) ;; notifications or libnotify
    ;;(add-hook 'after-init-hook #'mu4e-alert-enable-notifications)
    ;(add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display)
    ;(setq mu4e-alert-interesting-mail-query
    ;        (concat
    ;         "flag:unread"
    ;         " AND NOT flag:trashed"
    ;         " AND NOT maildir:\"/gmail/[Gmail].Spam\""
    ;         " AND NOT maildir:\"/gmailvl/[Gmail].Spam\""
    ;        ))
    
    
  12. Help
    
    ;;------------------ 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
    C-ch: toggle html2text method
    "))
    
    (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)
      )
    
    

8 Screenshot

mail.png

Author: Vianney Lebouteiller

Created: 2018-11-16 Fri 17:47

Validate