Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
570 lines (480 sloc) 22.7 KB

Install the required libraries

Install mu when on Linux

(when (eq system-type 'gnu/linux)
  ;; Install only if the prerequisites are satisfied
  (if (and (executable-find "autoreconf")
           (executable-find "xapian-config")
           (executable-find "libtool")
           (or (and (getenv "GMIME_CFLAGS")
                    (getenv "GMIME_LIBS"))
               (when (executable-find "pkg-config")
                 (zerop (shell-command "pkg-config --exists gmime-2.6")))))
      (progn (iqbal-install-packages '(mu4e mu4e-alert))
             (when (and (boundp 'mu4e-maildir)
                        (not (file-exists-p mu4e-maildir)))
               (warn "Please set `mu4e-maildir' to your maildir")))
    (message "You need to install `autoconf', `libxapian-dev', `libtool'
and `libgmime-2.6-dev' for building mu4e")))

Set mail-host-address

(setq mail-host-address "emacs.localhost")

Auto-complete addresses in ‘From’, ‘To’ field in message mode

Remove text-properties that interfere with company mode’s menu

(defun iqbal-strip-message-mode-problematic-text-props ()
    (goto-char (point-min))
    (when (search-forward-regexp (concat "^" mail-header-separator) nil t)
      (remove-text-properties (match-beginning 0) (match-end 0) '(rear-nonsticky)))))

(add-hook 'message-mode-hook 'iqbal-strip-message-mode-problematic-text-props)
(add-hook 'mu4e-compose-mode-hook 'iqbal-strip-message-mode-problematic-text-props)

Enable flyspell mode in message-mode

(add-hook 'message-mode-hook 'flyspell-mode)
(add-hook 'mu4e-compose-mode-hook 'flyspell-mode)

Offlineimap configuration

Use offlineimap to update messages in mu4e

(when (executable-find "offlineimap")
  (setq mu4e-get-mail-command "offlineimap"))

.offlineimaprc is a unix conf file

(add-to-list 'auto-mode-alist '("\\.offlineimaprc" . conf-mode))

General mu4e configuration

Use w3m to render html messages

(when (executable-find "w3m")
  (setq mu4e-view-prefer-html t)
  (setq mu4e-html2text-command "w3m -dump -T text/html"))

View images inline in message view buffer

(setq mu4e-view-show-images t)

(when (fboundp 'imagemagick-register-types)

Do not insert signature in sent emails

(setq mu4e-compose-signature-auto-include nil)

It is OK to use non-ascii characters

(setq mu4e-use-fancy-chars t)

Save attachments in ~/Downloads directory

(setq mu4e-attachment-dir "~/Downloads")

The information to displayed in the header line

(setq mu4e-headers-fields '((:human-date . 20)
                            (:tags . 12)
                            (:flags . 5)
                            (:from . 22)
                            (:to . 22)

Make bindings to open links in mu4e consistent with rest of emacs

(with-eval-after-load 'mu4e-view
  (define-key mu4e-view-clickable-urls-keymap iqbal-open-link #'mu4e~view-browse-url-from-binding))

Always show email addresses in mu4e

(setq mu4e-view-show-addresses t)

Do not display duplicate messages

(setq mu4e-headers-skip-duplicates t)

Kill message buffer after email is sent

(setq message-kill-buffer-on-exit t)

Do not use ido by default

(setq mu4e-completing-read-function 'completing-read)

Do not confirm on quit

(setq mu4e-confirm-quit nil)

Apply format=flowed to outgoing messages

mu4e manual says this should add format=flowed for autogoing messages

(defun iqbal-mu4e-toggle-hard-newlines ()
  (use-hard-newlines nil 'guess))

  ;; Result isn't as good as it sounds
;(add-hook 'mu4e-compose-mode-hook #'iqbal-mu4e-toggle-hard-newlines)

Additional ways to attach files

Making gnus-dired aware of mu4e

Autoload `gnus-dired-attach`

(autoload 'gnus-dired-attach "gnus-dired")

Monkey patch `gnus-dired-mail-buffers’ to use mu4e buffers to attach files`

(with-eval-after-load 'mu4e
  (defun iqbal-gnus-dired-mail-buffers ()
    "Return a list of active message buffers."
    (let (buffers)
        (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)))

  ;; Monkey patch gnus-dired to consider mu4e messages
  (advice-add #'gnus-dired-mail-buffers :override #'iqbal-gnus-dired-mail-buffers)

  (setq gnus-dired-mail-mode 'mu4e-user-agent))

Attach files from dired

Attaching files in selected region

(defun iqbal-mu4e-file-attach-files-from-region ()
  (let ((start (region-beginning))
        (end (region-end))
      (goto-char start)
      (while (< (point) end)
        (add-to-list 'files (dired-get-file-for-visit))
        (forward-line +1)))
    (gnus-dired-attach files)))

Attaching marked files

(eval-when-compile (require 'dired))

(defun iqbal-mu4e-file-attach-marked-files ()
  (gnus-dired-attach (dired-map-over-marks (dired-get-file-for-visit) nil)))

Tying them together

(defun iqbal-mu4e-attach-files-from-dired ()
  (if (region-active-p)


(with-eval-after-load 'dired
  (define-key dired-mode-map (kbd "a") #'iqbal-mu4e-attach-files-from-dired))

Attach files from helm-file-files


(with-eval-after-load 'helm-files
  (add-to-list 'helm-find-files-actions
               '("Attach files for mu4e" . iqbal-helm-mu4e-attach) t)

  (defun iqbal-helm-mu4e-attach (_file)
    (gnus-dired-attach (helm-marked-candidates))))

Additional actions for messages

Action to retag message

(with-eval-after-load 'mu4e
  (add-to-list 'mu4e-headers-actions
               (cons "retag" 'mu4e-action-retag-message)
  (add-to-list 'mu4e-view-actions
               (cons "retag" 'mu4e-action-retag-message)

Action to view current message in a browser

(defun iqbal-mu4e-action-view-in-browser (msg)
  "Modified version of original `mu4e-action-view-in-browser' this adds a meta
tag to charset, hardcoded to utf8 field, this makes the resulting document
render properly in browser.

The code assumes that the message is encoded in UTF-8, since finding the original
encoding will require parsing the original message and most of the times the
messages are utf-8 encoded"
  (let* ((html (mu4e-message-field msg :body-html))
         (txt (mu4e-message-field msg :body-txt))
         (tmpfile (format "%s%x.html" temporary-file-directory (random t))))
    (unless (or html txt)
      (mu4e-error "No body part for this message"))
      (let* ((msg-text (or html (concat "<pre>" txt "</pre>")))
             (html-format "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\"></head>%s</html>"))
        (insert (if (string-prefix-p "<html" msg-text)
                    ;; If the html starts with <html, it probably already
                    ;; has the encoding declared
                  ;; Otherwise add head with charset
                  (format html-format
                          ;; Wrap the text in body tag, usually not needed
                          ;; since modern browsers handle such malformed content
                          (format "%s%s%s"
                                  (unless (string-prefix-p "<body" msg-text) "<body>")
                                  (unless (string-prefix-p "<body" msg-text) "</body>")))))
        (write-file tmpfile)
        (browse-url (concat "file://" tmpfile))))))

(with-eval-after-load 'mu4e
  (add-to-list 'mu4e-view-actions '("View in browser" . iqbal-mu4e-action-view-in-browser)))

Action to view current message in w3m

(defun mu4e-action-view-in-w3m (msg)
  "View message in w3m"
  (let ((browse-url-browser-function #'w3m-browse-url))
    (iqbal-mu4e-action-view-in-browser msg)))

(with-eval-after-load 'mu4e
  (when (locate-library "w3m")
    (add-to-list 'mu4e-view-actions '("open in w3m" . mu4e-action-view-in-w3m))))

Action to import appointments from ical files

Functions to parse ical file

(require 'org-import-icalendar)

(defun iqbal-parse-ical-event (event)
  ;; org-import-icalendar expects e to be bound
  (let ((e event))
    (list :location (iqbal-cleanup-ical-text (icalendar--get-event-property event 'LOCATION))
          :summary (iqbal-cleanup-ical-text (icalendar--convert-string-for-import
                                             (or (icalendar--get-event-property event 'SUMMARY)
                                                 "No summary")))
          :description (iqbal-cleanup-ical-text (icalendar--get-event-property event 'DESCRIPTION))
          :date (org-import-icalendar-get-org-timestring event)
          :uid (icalendar--get-event-property event 'UID)
          :attachment (icalendar--get-event-property event 'ATTACH)
          :attendees (icalendar--get-event-properties event 'ATTENDEE)
          :status (icalendar--get-event-property event 'STATUS)
          :last-modified (icalendar--get-event-property event 'LAST-MODIFIED))))

(defun iqbal-parse-ical-file (file)
    (message (format "Parsing appts from %s" file))
    (insert-file-contents file)
    (goto-char (point-min))
    (let* ((ical-data (icalendar--read-element nil nil))
           (zone-map (icalendar--convert-all-timezones ical-data))
           (events (icalendar--all-events ical-data)))
      (mapcar #'iqbal-parse-ical-event events))))

Functions to convert the parsed ical data to appt

(defun iqbal-cleanup-ical-text (text)
  (and (stringp text)
       (replace-regexp-in-string "\\\\," "," (replace-regexp-in-string "\\\\n" "\n" text))))

(defun iqbal-make-appt-from-parsed-ical-data (data source)
  (with-current-buffer (find-file-noselect (iqbal-get-file-in-data-directory "agenda/"))
    (goto-char (point-max))
    (insert (format "* TODO %s\n\n%s\n"
                    (plist-get data :summary)
                    (iqbal-indent-text (plist-get data :description) 2)
    (org-schedule nil (plist-get data :date))
    (org-entry-put (point) "ID" (plist-get data :uid))
    (org-id-add-location (plist-get data :uid) (buffer-file-name (buffer-base-buffer)))
    ;; Add attachment if present
    (when (and (plist-get data :attachment)
               (not (string= (plist-get data :attachment) "")))
      (org-entry-put (point)
                     (plist-get data :attachment)))

    ;; Add location if persent
    (when (and (plist-get data :location)
               (not (string= (plist-get data :location) "")))
      (org-entry-put (point)
                     (plist-get data :location)))

    ;; Add attendees if present
    (when (plist-get data :attendees)
      (org-entry-put (point)
                     (string-join (plist-get data :attendees) ", ")))

    (when (plist-get data :last-modified)
      (org-entry-put (point)
                     (plist-get data :last-modified)))

    (when (plist-get data :status)
      (ignore-errors (org-todo (upcase (plist-get data :status)))))

    (insert (format "\nSource: %s\n" source))))

(defun iqbal-process-existing-appt (data source)
    (org-id-goto (plist-get data :uid))
    (let ((last-modified (org-entry-get nil "LASTMODIFIED")))
      ;; Update only if this event is newer
      (when (and (plist-get data :last-modified)
                 (or (not last-modified)
                     (time-less-p (date-to-time last-modified)
                                  (date-to-time (plist-get data :last-modified)))))
        (org-set-property "ID" (concat (plist-get data :uid) "_archived_" (format-time-string "%s")))
        (iqbal-make-appt-from-parsed-ical-data data source)))))

(defun iqbal-make-appts-from-parsed-ical-data (data source)
  (dolist (ical-data data)
    (let ((exisiting-appt (org-id-find (plist-get ical-data :uid))))
      (if (not exisiting-appt)
          (iqbal-make-appt-from-parsed-ical-data ical-data source)
        (iqbal-process-existing-appt ical-data source)))))

Hooking the above into mu4e

(defun mu4e-action-appt-from-ics (msg)
  (dolist (index (hash-table-keys mu4e~view-attach-map))
    (let* ((attachment (mu4e~view-get-attach msg index))
           (msgpath (plist-get msg :path))
           (tmpdir (make-temp-file "mu4e" t))
           (dest (string-join (list tmpdir (plist-get attachment :name)) "/"))
           (source-link (org-store-link nil)))
      (when (string= (plist-get attachment :mime-type)
        ;; Calling the server to save does not work reliably specifically
        ;; sometimes the file extracted is blank when Emacs reads it, though
        ;; if read later it has the extracted contents, maybe file system
        ;; cache issue, who knows! :confused:
        (shell-command (format "%s extract %s --parts=%d --target-dir=%s"
                               (shell-quote-argument msgpath)
                               (plist-get attachment :index)
        (iqbal-make-appts-from-parsed-ical-data (iqbal-parse-ical-file dest) source-link)
        (message (format "Imported %s" (plist-get attachment :name)))))))

(with-eval-after-load 'mu4e
  (add-to-list 'mu4e-view-actions (cons "ical to appt" 'mu4e-action-appt-from-ics) t))

Auto update configuration

(setq mu4e-hide-index-messages t)
(setq mu4e-get-mail-command "offlineimap")
(setq mu4e-update-interval 300)

Additional bookmarks for mu4e

(defun iqbal-get-all-mail-query ()
  (require 'subr-x)
  (require 'cl-lib)
  (string-join (mapcar (lambda (maildir) (format "maildir:\"%s\"" maildir))
                        (lambda (maildir) (or (string-match-p "[Ss]pam" maildir) (string-match-p "[Tt]rash" maildir)))
                        (cl-set-difference (mu4e-get-maildirs) (list mu4e-trash-folder) :test #'string=)))
               " OR "))

(with-eval-after-load 'mu4e
  (add-to-list 'mu4e-bookmarks `(,(iqbal-get-all-mail-query) "All mail" ?a))
  (add-to-list 'mu4e-bookmarks '("flag:attach" "Mails with attachement" ?T) t))

Start mu4e

(defun iqbal-start-mu4e-bg ()
  "Start in background avoiding any prompts and ignoring errors"
  (when (and (require 'mu4e nil t)
             (file-directory-p mu4e-maildir)
             (file-directory-p (concat mu4e-maildir mu4e-sent-folder))
             (file-directory-p (concat mu4e-maildir mu4e-drafts-folder))
             (file-directory-p (concat mu4e-maildir mu4e-trash-folder)))
    (ignore-errors (mu4e t)
                   (setq mail-user-agent 'mu4e-user-agent))))

(add-hook 'after-init-hook #'iqbal-start-mu4e-bg)

mu4e-alert configuration

(defun iqbal-enable-mu4e-alert ()
  (when (locate-library "mu4e-alert")
    (mu4e-alert-set-default-style (if (executable-find "notify-send")
    (setq mu4e-alert-interesting-mail-query "flag:unread AND NOT flag:trashed AND NOT maildir:\"/professional/Bulk Mail\" AND NOT maildir:\"/personal/Bulk Mail\"")

(add-hook 'after-init-hook #'iqbal-enable-mu4e-alert)

Configuration for sending mail

Sending mail from multiple smtp accounts when using mu4e

(defvar iqbal-mu4e-account-alist nil "List of accounts in format specified here []")

(defun iqbal-mu4e-set-account ()
  "Set the account for composing a message."
  (require 'cl-lib)
  ;; If we are about to compose a reply retrieve try retrieving the
  ;; the account corresponding to 'to' field of email
  (let* ((inferred-account (when mu4e-compose-parent-message
                             (let ((receiving-email (cdar (mu4e-message-field mu4e-compose-parent-message
                               (caar (cl-remove-if-not (lambda (account)
                                                         (string= (cadr (assoc 'user-mail-address account))
         ;; Otherwise read the account to use from the user
         (read-account (when (and iqbal-mu4e-account-alist (not inferred-account))
                         (completing-read (format "Compose with account: (%s) "
                                                  (mapconcat #'car
                                                             iqbal-mu4e-account-alist "/"))
                                          (mapcar #'car iqbal-mu4e-account-alist)
                                          nil t nil nil (caar iqbal-mu4e-account-alist))))
         ;; Retrieve the variables corresponding to account
         (account-vars (cdr (assoc (or inferred-account read-account) iqbal-mu4e-account-alist))))
    (when account-vars
      ;; Set the variables
      (mapc #'(lambda (var)
                (set (car var) (cadr var)))

(add-hook 'mu4e-compose-pre-hook 'iqbal-mu4e-set-account)

Prefer .authinfo.gpg for credentials

(with-eval-after-load 'auth-source
  (setq auth-sources (cons "~/.authinfo.gpg"
                           (delete "~/.authinfo.gpg" auth-sources))))

Send mail using smtp

(setq send-mail-function 'smtpmail-send-it)

Integration with org-mode

Register a handler to open links to mu4e messages

(when (locate-library "org-mu4e")
  (autoload 'org-mu4e-open "org-mu4e")
  (with-eval-after-load 'org
    (org-add-link-type "mu4e" 'org-mu4e-open)))

Load org-mu4e on loading mu4e

(with-eval-after-load 'mu4e (require 'org-mu4e nil t))

Advice mu4e~proc-sentinel so that path to mu binary is copied to clipboard

This is needed since in case mu is installed using el-get, which is buried deep in .emacs.d folder and might not be in PATH, as such it cannot be run directly from shell. The following advice copies the path to mu to clipboard, so that it can be directly run from shell

(defun iqbal-advise-mu4e~proc-sentinel (orig &rest args)
  (condition-case err
      (apply orig args)
    (error (progn (kill-new mu4e-mu-binary)
                  (user-error "Failed to start mu. %s. Path to mu binary (%s) copied to clipboard."
                         (error-message-string err)

(advice-add 'mu4e~proc-sentinel :around #'iqbal-advise-mu4e~proc-sentinel)

Advice mu4e-view-quit to switch to main buffer no header buffer

(defun iqbal-mu4e-switch-to-main-buffer-maybe (&rest ignored)
  (unless (eq (current-buffer) mu4e~headers-buffer)
    (when (get-buffer mu4e~main-buffer-name)
      (switch-to-buffer mu4e~main-buffer-name))))

(advice-add 'mu4e~view-quit-buffer :after #'iqbal-mu4e-switch-to-main-buffer-maybe)

Keybindings for mu4e

(define-key iqbal-launcher-map "m" #'mu4e)

(with-eval-after-load 'mu4e
  (define-key mu4e-view-mode-map (kbd "U") #'mu4e-headers-rerun-search)
  (define-key mu4e-main-mode-map (kbd "q") #'bury-buffer)
  (define-key mu4e-main-mode-map (kbd "Q") #'mu4e-quit))