1. Qiita
  2. 投稿
  3. Emacs
  • 7
    いいね
  • 0
    コメント

mu4eとは

mu というローカルメールディレクトリ管理ソフトウェアのemacsクライアントで、mu4e は"mu for emacs"の略です。検索機能を中心に設計されているので、当然ながら検索が充実しているのが特徴です。

概略

リモートのIMAPアカウントをローカルのメールボックスにmbsyncで同期(imapnotifyでIMAP IDLEサポート)、mu4eを使ってemacsからメールを閲覧するという仕組みを構築します。

muはローカルでの検索とメール操作のみを担当しますので、別途同期ソフトウェアmbsyncが必要になります。さらにmbsyncはIMAP IDLEをサポートしていないので、imapnotifyを利用して補助します。imapnotifyの起動管理にprodigy.elを利用します。

インストール

環境はmacOS、emacsはすでに機能している状態を想定します。各種ソフトウェアのインストールをまず行います。Emacs以外のソフトウェアのインストールには、Homebrew を使います。prodigy.elは、MELPA からインストールしますので、こちらも設定されているものとします。

muおよびmu4e

EMACS=$(which emacs) brew install mu --with-emacs --HEAD

HEADの最新版が必要なようです。emacsのバージョンがチェックされるので、先に変数を宣言しているようです。これはTerminal.appで呼びだされるemacsもmacOS標準付属ではなくHomebrewのものを用意する必要があるかもしれません。私は基本的にGUI版のemacsを使いますが、Homebrewでコマンドラインのemacsも最新版をいれています。emacsパッケージであるmu4eは、muの一部として含まれているので上記で一緒にインストールされます。

isync/mbsync

brew install isync

isyncがソフトウェア名でmbsyncがコマンド名です。これはリモートのIMAPをローカルのMaildirと同期させるためのソフトウェアです。

imapnotify

brew install node
npm init
npm install imapnotify --no-optional

これはNode.jsを利用するので、そこからインストールが必要です。npmはNode.jsのパッケージ管理システムのようです。–no-optionalがないと余計なパッケージをインストールしようとして、しかもエラーになるのでつけています。

prodigy.el

M-x package-install RET
prodigy RET

これはMELPAが設定されていれば、emacs内で、これを行うだけでインストールできると思います。

設定ファイル

iCloud、個人用Gmail、大学院のGmailの三つを設定しています。mbsyncとmu4eまでで基本の機能は完成します。

mbsync

~/.mbsyncrc に下記のようなものを設定します。かなり複雑なのですが、アカウントを設定、メールボックスをリモートとローカルに定義、メールボックス(複数可)をChannelとして定義(これが同期の単位)、さらにChannelをGroupとしてまとめるという形式をとります。下記ではinboxだけのGroup (Group inbox-only)と、全メールボックスのGroup (Group all)を定義することで、状況によって同期する範囲を変更できるようにしています。

当然パスワードの管理が必要になりますので、これはmacOS標準のKeychain Access.appでパスワードのエントリーを作成し、新規のApp-specific passwordをiCloudやGmailで作成して保管しておきます。これにより設定ファイルにパスワードを書き込まないで security find-generic-password -s mbsync-gmail -w のような感じで管理できます。

ローカルにフォルダが必要になるので作成してください。私は ~/.maildir/ を親フォルダにしてそのなかに三つのアカウントのサブフォルダがある形になっています。初回の同期は mbsync -a で全アカウントを同期すると良いのですが、iCloudはサイズが大きすぎてフリーズしてしまったので、 timelimit コマンドを使って、同期開始してしばらくしたら強制終了をループさせるという方法で一晩かけてやりました。

Create Both, Expunge Both, SyncState * はメールボックスごとに指定できますが、ここでは先頭で全メールボックスに宣言しています。

## mbsync configuration

## Reading IMAP Mail in Emacs on OSX
## http://www.ict4g.net/adolfo/notes/2014/12/27/EmacsIMAP.html
## Automated gmail backup via IMAP
## https://blog.rectalogic.com/2007/11/automated-gmail-backup-via-imap.html
## isync
## https://wiki.archlinux.org/index.php/Isync
## Drowning in Email; mu4e to the Rescue.
## http://www.macs.hw.ac.uk/~rs46/posts/2014-01-13-mu4e-email-client.html
## mbsync - synchronize IMAP4 and Maildir mailboxes
## http://isync.sourceforge.net/mbsync.html
## Troubleshooting Mbsync Duplicate UID Errors (Change mu4e setting)
## http://tiborsimko.org/mbsync-duplicate-uid.html
## Syncing mails with mbsync (instead of OfflineIMAP)
## https://bloerg.net/2013/10/09/syncing-mails-with-mbsync-instead-of-offlineimap.html

## Securely storing scriptable credentials in the OSX Keychain
## https://blog.starkandwayne.com/2015/12/07/one-keychain-to-rule-them-all/
## The GNU Privacy Guard
## https://www.gnupg.org
## pass the standard unix password manager
## http://www.passwordstore.org
## Using pass for offlineimap
## https://github.com/sup-heliotrope/sup/wiki/Securely-Store-Password
## Creating GPG Keys
## https://fedoraproject.org/wiki/Creating_GPG_Keys#Creating_GPG_Keys_Using_the_Command_Line
## Pass Unix Password Manager
## http://www.tricksofthetrades.net/2015/07/04/notes-pass-unix-password-manager/
## Using gpg-agent on OS X
## https://wincent.com/wiki/Using_gpg-agent_on_OS_X

###
### Global Defaults
## These are commont to all Channels, thus, stated here.
## They can be Channel-specific.
Create Both
Expunge Both
SyncState *


###
### GMAIL
### ACCOUNT INFORMATION
IMAPAccount gmail
Host imap.gmail.com
User CHANGETOYOURADDRESS@gmail.com
PassCmd "security find-generic-password -s mbsync-gmail -w"
AuthMechs LOGIN
SSLType IMAPS
# SSLVersions SSLv3
CertificateFile /usr/local/etc/openssl/cert.pem

# THEN WE SPECIFY THE LOCAL AND REMOTE STORAGE
# - THE REMOTE STORAGE IS WHERE WE GET THE MAIL FROM (E.G., THE
#   SPECIFICATION OF AN IMAP ACCOUNT)
# - THE LOCAL STORAGE IS WHERE WE STORE THE EMAIL ON OUR COMPUTER
### REMOTE STORAGE (USE THE IMAP ACCOUNT SPECIFIED ABOVE)

IMAPStore gmail-remote
Account gmail

### LOCAL STORAGE (CREATE DIRECTORIES with mkdir -p Maildir/gmail)
MaildirStore gmail-local
Path ~/.maildir/gmail/
Inbox ~/.maildir/gmail/inbox

### CONNECTIONS SPECIFY LINKS BETWEEN REMOTE AND LOCAL FOLDERS
#
# CONNECTIONS ARE SPECIFIED USING PATTERNS, WHICH MATCH REMOTE MAIl
# FOLDERS. SOME COMMONLY USED PATTERS INCLUDE:
#
# 1 "*" TO MATCH EVERYTHING
# 2 "!DIR" TO EXCLUDE "DIR"
# 3 "DIR" TO MATCH DIR
#
# FOR INSTANCE IN THE SPECIFICATION BELOW:
#
# gmail-inbox gets the folder INBOX, ARCHIVE, and everything under "ARCHIVE*"
# gmail-trash gets only the "[Gmail]/Trash" folder and stores it to the local "trash" folder

Channel gmail-inbox
Master :gmail-remote:
Slave :gmail-local:
Patterns "INBOX"

Channel gmail-trash
Master :gmail-remote:"[Gmail]/Trash"
Slave :gmail-local:trash

Channel gmail-sent
Master :gmail-remote:"[Gmail]/Sent Mail"
Slave :gmail-local:sent

Channel gmail-archive
Master :gmail-remote:
Slave :gmail-local:
Patterns "Arch*"

### GROUPS PUT TOGETHER CHANNELS, SO THAT WE CAN INVOKE
# MBSYNC ON A GROUP TO SYNC ALL CHANNELS
#
# FOR INSTANCE: "mbsync gmail" GETS MAIL FROM
# "gmail-inbox", "gmail-sent", and "gmail-trash"
#
Group gmail
Channel gmail-inbox
Channel gmail-sent
Channel gmail-trash
Channel gmail-archive


###
### harvard
IMAPAccount harvard
Host imap.gmail.com
User CHANGETOYOURADDRESS@mail.harvard.edu
PassCmd "security find-generic-password -s mbsync-harvard -w"
AuthMechs LOGIN
SSLType IMAPS
# SSLVersions SSLv3
CertificateFile /usr/local/etc/openssl/cert.pem

IMAPStore harvard-remote
Account harvard

MaildirStore harvard-local
Path ~/.maildir/harvard/
Inbox ~/.maildir/harvard/inbox

Channel harvard-inbox
Master :harvard-remote:
Slave :harvard-local:
Patterns "INBOX"

Channel harvard-trash
Master :harvard-remote:"[Gmail]/Trash"
Slave :harvard-local:trash

Channel harvard-sent
Master :harvard-remote:"[Gmail]/Sent Mail"
Slave :harvard-local:sent

Group harvard
Channel harvard-inbox
Channel harvard-sent
Channel harvard-trash


###
### iCloud
## Reading IMAP Mail in Emacs on OSX (See Comments)
## http://www.ict4g.net/adolfo/notes/2014/12/27/EmacsIMAP.html
## iCloud: Mail server settings for email clients
## https://support.apple.com/en-gb/HT202304
## Using app-specific passwords (store password using Keychain Access.app; access with security...)
## https://support.apple.com/en-us/HT204397
## Initial iCloud mbsync hangs a lot
## http://sourceforge.net/p/isync/bugs/20/
## Using timelimit for mbsync
## https://groups.google.com/forum/#!topic/mu-discuss/FLz4FcECo3U
##
## Brute-force initial pull using a for loop
## for i in {1..1000}; do timelimit -t 20 mbsync icloud; done

IMAPAccount icloud
Host imap.mail.me.com
User CHANGETOYOURADDRESS@mac.com
PassCmd "security find-generic-password -s mbsync-icloud -w"
Port 993
AuthMechs LOGIN
SSLType IMAPS
SSLVersions TLSv1

IMAPStore icloud-remote
Account icloud

MaildirStore icloud-local
Path  ~/.maildir/icloud/
Inbox ~/.maildir/icloud/inbox
Trash "Deleted Messages"

Channel icloud-inbox
Master :icloud-remote:
Slave :icloud-local:
## Use * to get them all. Strings such as "INBOX" to specify
## Patterns *
## Automatically create missing mailboxes, both locally and on the server

Channel icloud-all
Master :icloud-remote:
Slave :icloud-local:
## Use * to get them all. Strings such as "INBOX" to specify
Patterns *
## Automatically create missing mailboxes, both locally and on the server

Group icloud
Channel icloud-all


###
### INBOX-only group (gmail, harvard, and icloud)
Group inbox-only
Channel gmail-inbox
Channel harvard-inbox
Channel icloud-inbox


###
### All-box group (gmail, harvard, and icloud)
Group all
Channel gmail-inbox
Channel gmail-sent
Channel gmail-trash
Channel gmail-archive
Channel harvard-inbox
Channel harvard-sent
Channel harvard-trash
Channel icloud-all

mu

mu自体には設定ファイルは不要です。勝手に .mu フォルダを作成して一時ファイルを保管するようです。

mu4e

第一部

emacs内の設定です。こちらが第一部。mu4eは通常のパッケージとしてインストールしないので、.emacs.dの外にあります。 "/usr/local/Cellar/mu/HEAD-1f232b6/share/emacs/site-lisp/mu/mu4e/" をload-path指定する必要があります。パスがバージョン依存になるみたいなので注意。

ここで工夫した点は、 modify-mu4e-get-mail-command というコマンドを定義して、 mu4e-update-mail-and-index に:beforeアドバイスとして追加(直前に実行させること)しているところです。これは自分でアップデートコマンドを呼ぶと、mbsyncをinboxのみ同期として呼びだします(この方が速いので)。バックグラウンドでの自動同期ではmbsyncが全メールボックスの設定でよばれます。手動で全メールボックスの同期を行うために mu4e-update-mail-and-index-all というコマンドも定義しています。

mu4eはメールの作成と送信はemacsのデフォルトの機能に投げているようです。これは送信時にブロックを起こしてしまいます。smtpmail-asyncでバックグラウンドにもできるのですが、動作があやしく推奨されていません。ここでは行っていませんがPostfixなどに渡す方法があるようです。

mu4e-alert.elという別パッケージで各種の通知ができます。選択肢はこのパッケージで定義されている alert-styles という変数でみられます。macOSのNotification Centerをterminal-notifierで叩いていましたが、目障りなのでコメントアウトしています。

;;;
;;; General configuration
;; Default in sending e-mail
(setq user-full-name "Kazuki Yoshida")

;;;  SMTP configuration
;; use msmtp
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq sendmail-program "msmtp")
;; tell msmtp to choose the SMTP server according to the from field in the outgoing email
(setq message-sendmail-extra-arguments '("--read-envelope-from"))
(setq message-sendmail-f-is-evil 't)

;;;  smtpmail-async.el
;; https://github.com/jwiegley/emacs-async
;; https://www.djcbsoftware.nl/code/mu/mu4e/Writing-messages.html
;; DO NOT USE THIS. SENDING CAN FAIL AND GO UNNOTICED.
(use-package smtpmail-async
  :disabled t
  :config
  (setq send-mail-function 'async-smtpmail-send-it)
  (setq message-send-mail-function 'async-smtpmail-send-it))


;;;
;;; mu4e-related
;; http://www.djcbsoftware.nl/code/mu/mu4e.html
;; https://www.emacswiki.org/emacs/mu4e
;;
;; Manage your gmail account in emacs with mu4e
;; https://gist.github.com/areina/3879626
;; installing mu and mu4e with homebrew with emacs from emacsforosx
;; http://blog.danielgempesaw.com/post/43467552978/installing-mu-and-mu4e-with-homebrew-with-emacs
;; MU4E TUTORIALS
;; http://pragmaticemacs.com/mu4e-tutorials/
;; The Ultimate Emailing Agent with Mu4e and Emacs
;; http://tech.memoryimprintstudio.com/the-ultimate-emailing-agent-with-mu4e-and-emacs/
;;
;; Install infrastructure
;;
;; Install HEAD mu along with mu4e following below
;; make sure which emacs gives the path to a new emacs (23+)
;; https://github.com/Homebrew/homebrew/issues/16504#issuecomment-11394215
;; EMACS=$(which emacs) brew install mu --with-emacs --HEAD
;; ./configure --prefix=/usr/local/Cellar/mu/HEAD --with-lispdir=/usr/local/Cellar/mu/HEAD/share/emacs/site-lisp/mu
;;
;; These unix tools also have to be installed and configured elsewhere.
;; mbsync (isync package) for IMAP-Maildir syncing
;; msmtp for sending messages
;; pass and gpg for encrypted password handling (instead macOS security command in use)
;;
;;
;;; mu4e.el
;; Reading IMAP Mail in Emacs on OSX
;; http://www.ict4g.net/adolfo/notes/2014/12/27/EmacsIMAP.html
;; Execute the following before first invocation
;; mu index --maildir=~/.maildir
(use-package mu4e
  :commands (mu4e)
  :init
  ;; Check for mu4e directory before invoking all the following
  (let ((mu4e-dir "/usr/local/Cellar/mu/HEAD-1f232b6/share/emacs/site-lisp/mu/mu4e/"))
    (when (file-exists-p mu4e-dir)
      (add-to-list 'load-path mu4e-dir)))
  ;;
  :config
  ;; tell mu4e where my Maildir is
  (setq mu4e-maildir "~/.maildir")
  ;; mu binary (backend)
  (setq mu4e-mu-binary "/usr/local/bin/mu")
  ;;
  ;;
;;;  Syncing
  ;; tell mu4e how to sync email
  ;; Defined by defcustom, thus, a dynamic variable.
  ;; Using timelimit for mbsync to limit execution time
  ;; https://groups.google.com/forum/#!topic/mu-discuss/FLz4FcECo3U
  ;; inbox-only is a group of inbox channels and does not sync other boxes
  (if (executable-find "timelimit")
      (setq mu4e-get-mail-command "timelimit -t 60 mbsync inbox-only")
    (setq mu4e-get-mail-command "mbsync inbox-only"))
  ;;
  ;; Define a function to change mbsync behavior when called interactively
  (defun modify-mu4e-get-mail-command (run-in-background)
    "Manipulate mu4e-get-mail-command depending on interactive status"
    ;; "P" is for universal argument
    ;; http://ergoemacs.org/emacs/elisp_universal_argument.html
    (interactive "P")
    (if (called-interactively-p 'interactive)
        ;; If interactive, then inbox-only
        (if (executable-find "timelimit")
            (setq mu4e-get-mail-command "timelimit -t 60 mbsync inbox-only")
          (setq mu4e-get-mail-command "mbsync inbox-only"))
      ;; Otherwise, all folders
      (if (executable-find "timelimit")
          (setq mu4e-get-mail-command "timelimit -t 180 mbsync all")
        (setq mu4e-get-mail-command "mbsync all"))))
  ;; :before advice to mainpulate mu4e-get-mail-command variable
  (advice-add 'mu4e-update-mail-and-index :before #'modify-mu4e-get-mail-command)
  ;; (advice-remove 'mu4e-update-mail-and-index #'modify-mu4e-get-mail-command)
  ;;
  ;; Function to sync all folders
  (defun mu4e-update-mail-and-index-all ()
    "Update email with more extensive folder syncing"
    (interactive)
    ;; (setq mu4e-get-mail-command "timelimit -t 120 mbsync all")
    ;; This is considered non-interactive call with respect to
    ;; mu4e-update-mail-and-index. So it's all-box anyway.
    (mu4e-update-mail-and-index nil))
  (define-key 'mu4e-main-mode-map (kbd "A-u")    'mu4e-update-mail-and-index-all)
  (define-key 'mu4e-headers-mode-map (kbd "A-u") 'mu4e-update-mail-and-index-all)
  (define-key 'mu4e-view-mode-map (kbd "A-u")    'mu4e-update-mail-and-index-all)
  ;; indexing only
  (define-key 'mu4e-main-mode-map (kbd "s-i")    'mu4e-update-index)
  (define-key 'mu4e-headers-mode-map (kbd "s-i") 'mu4e-update-index)
  (define-key 'mu4e-view-mode-map (kbd "s-i")    'mu4e-update-index)
  ;;
  ;; Change file UID when moving (necessary for mbsync, but not for offlineimap)
  ;; https://groups.google.com/forum/m/#!topic/mu-discuss/8c9LrYYpxjQ
  ;; http://www.djcbsoftware.nl/code/mu/mu4e/General.html
  (setq mu4e-change-filenames-when-moving t)
  ;; Update interval
  (setq mu4e-update-interval (* 60 15))
  ;; Do not occupy the minibuffer with "Indexing..."
  ;; https://www.djcbsoftware.nl/code/mu/mu4e/General.html
  (setq mu4e-hide-index-messages t)
  ;; Whether to cache the list of maildirs
  (setq mu4e-cache-maildir-list t)
  ;; Whether to run a cleanup phase after indexing (avoids ghost messages)
  (setq mu4e-index-cleanup t)
  ;; Date stamp check only; miss message that are modified outside mu
  (setq mu4e-index-lazy-check nil)
  ;;
;;;  Dynamic folder selection (configured elsewhere)
  ;;
  ;;
;;;  Main view configuration
  ;;
  ;;
;;;  Header view configuration
  (setq mu4e-split-view 'vertical)
  ;; number of columns
  (setq mu4e-headers-visible-columns 100)
  (defun set-header-columns-half-frame ()
    "Make header split frame into half in mu4e split view"
    (interactive)
    (setq mu4e-headers-visible-columns
          ;; Set to 1/2 of frame width in default characters
          (/ (/ (frame-text-width) (frame-char-width)) 2)))
  (add-hook 'mu4e-headers-mode-hook 'set-header-columns-half-frame)
  ;;
  ;; Show related messages in addition to search results
  (setq mu4e-headers-include-related nil)
  ;; Maximum number of results to show
  (setq mu4e-headers-results-limit 500)
  ;; Whether to automatically update headers if any indexed changes appear
  (setq mu4e-headers-auto-update t)
  ;; http://www.djcbsoftware.nl/code/mu/mu4e/Other-search-functionality.html#Skipping-duplicates
  (setq mu4e-headers-skip-duplicates t)
  ;; 4.4 Sort order and threading
  ;; https://www.djcbsoftware.nl/code/mu/mu4e/Sort-order-and-threading.html
  ;; :date, :subject, :size, :prio, :from, :to
  (setq mu4e-headers-sort-field :date)
  ;; Threading off by default. use P to turn on.
  (setq mu4e-headers-show-threads nil)
  (setq mu4e-use-fancy-chars nil)
  ;; ‘apply’ automatically apply the marks before doing anything else
  (setq mu4e-headers-leave-behavior 'apply)
  ;;
  ;;
;;;  Message view configuration
  ;; Whether to automatically display attached images in the message
  (setq mu4e-view-show-images t)
  (setq mu4e-view-show-addresses t)
  ;; Actions
  ;; https://www.djcbsoftware.nl/code/mu/mu4e/Actions.html
  (setq mu4e-view-actions
        '(("capture message"  . mu4e-action-capture-message)
          ("view as pdf"      . mu4e-action-view-as-pdf)
          ("show this thread" . mu4e-action-show-thread)
          ("browser"          . mu4e-action-view-in-browser)))
  ;;
  ;; Displaying rich-text messages
  ;; https://www.djcbsoftware.nl/code/mu/mu4e/Displaying-rich_002dtext-messages.html
  (setq mu4e-view-prefer-html nil)
  ;; html converters use first one that exists
  (cond ((executable-find "/usr/local/bin/html2text")
         (setq mu4e-html2text-command "/usr/local/bin/html2text -utf8 -nobs -width 72"))
        ((executable-find "w3m")
         (setq mu4e-html2text-command "w3m -T text/html"))
        ((executable-find "textutil")
         (setq mu4e-html2text-command "textutil -stdin -format html -convert txt -stdout"))
        ;; This one gives live links, but seems to be slow.
        (t (progn (require 'mu4e-contrib)
                  (setq mu4e-html2text-command 'mu4e-shr2text)
                  (add-hook 'mu4e-view-mode-hook
                            (lambda()
                              ;; try to emulate some of the eww key-bindings
                              (local-set-key (kbd "<tab>") 'shr-next-link)
                              (local-set-key (kbd "<backtab>") 'shr-previous-link))))))
  ;; pdf view
  ;; https://www.djcbsoftware.nl/code/mu/mu4e/Installation.html
  ;; https://github.com/djcb/mu/issues/443
  ;; http://stackoverflow.com/questions/24194357/glib-h-file-not-found-on-max-osx-10-9
  (setq mu4e-msg2pdf nil)
  ;; Default directory for saving attachments.
  (setq mu4e-attachment-dir (expand-file-name "~/Downloads"))
  ;;
;;;  Editor view configuration
  ;; Do not drop myself from cc list
  (setq mu4e-compose-keep-self-cc t)
  ;; Always CC myself
  ;; http://www.djcbsoftware.nl/code/mu/mu4e/Compose-hooks.html
  (add-hook 'mu4e-compose-mode-hook
            (defun my-add-header ()
              "Add CC: and Bcc: to myself header."
              (save-excursion (message-add-header
                               (concat "CC: " "\n")
                               (concat "Bcc: " user-mail-address "\n")))))
  ;; Flyspell
  (add-hook 'mu4e-compose-mode-hook 'flyspell-mode-on)
  ;; org-mode
  ;; (add-hook 'mu4e-compose-mode-hook 'mu4e-org-mode)
  ;; (remove-hook 'mu4e-compose-mode-hook 'mu4e-org-mode)
  ;;
  ;; A.9 Attaching files with dired
  ;; http://www.djcbsoftware.nl/code/mu/mu4e/Attaching-files-with-dired.html
  (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)
  ;;
;;; mu4e-alert.el
  ;; Notification for mu4e
  ;; https://github.com/iqbalansari/mu4e-alert
  (require 'mu4e-alert)
  ;;
  (mu4e-alert-enable-mode-line-display)
  ;;
  ;; choose from alert-styles
  (mu4e-alert-set-default-style 'message)
  ;; Using macOS notifier (too annoying)
  ;; (when (executable-find "terminal-notifier")
  ;;   (setq alert-notifier-command "terminal-notifier")
  ;;   ;; choose from alert-styles
  ;;   (mu4e-alert-set-default-style 'notifier)
  ;;   ;; Immediately enable without waiting
  ;;   ;; This entire use-package expression waits for M-x mu4e
  ;;   (mu4e-alert-enable-notifications)
  ;;   ;; This is equivalent without immediate execution
  ;;   ;; (add-hook 'mu4e-index-updated-hook #'mu4e-alert-notify-unread-mail-async)
  ;;   ;; after-init-hook does not work in delayed use-package expression
  ;;   ;; (add-hook 'after-init-hook #'mu4e-alert-enable-notifications)
  ;;   )
  ;; Interesting mail only
  (setq mu4e-alert-interesting-mail-query
        (concat
         "flag:unread"
         " AND NOT flag:trashed"))
  ;;
  ;; Handcrafted notifier (use if the one above does not work)
  ;; http://www.djcbsoftware.nl/code/mu/mu4e/Retrieving-mail.html#Retrieving-mail
  ;; (add-hook 'mu4e-index-updated-hook
  ;;           (defun new-mail-notifier ()
  ;;             (shell-command "/usr/local/bin/terminal-notifier -message 'maildir updated'")))
  ;;
;;; helm-mu.el
  (require 'helm-mu)
  ;; https://github.com/emacs-helm/helm-mu
  ;; brew install gnu-sed --with-default-names
  ;; Default search string
  (setq helm-mu-default-search-string "")
  ;; Only show contacts first recorded after a certain date
  (setq helm-mu-contacts-after "2010-01-01")
  ;; Only show contacts who sent you emails directly
  (setq helm-mu-contacts-personal t))

第二部

さらに第二部。こっちの設定は個人情報が入るので別ファイルにしています(メインファイルはgithubで公開している)。ダイナミックフォルダという機能があって、送信元のアドレスによってどのSentフォルダに入れるかを制御したりできます。

便利なのがBookmarkという機能で検索条件でApple Mail.appでいうところのSmart Folderを定義できます。未読でゴミ箱ではないやつだとかいろいろ設定しています。

;;; Default e-mail address
(setq user-mail-address "CHANGETOYOURADDRESS@mac.com")

;;; My e-mail address list
(setq mu4e-user-mail-address-list '("CHANGETOYOURADDRESS@mac.com"
                                    "CHANGETOYOURADDRESS@me.com"
                                    "CHANGETOYOURADDRESS@icloud.com"

                                    "CHANGETOYOURADDRESS@mac.com"
                                    "CHANGETOYOURADDRESS@me.com"
                                    "CHANGETOYOURADDRESS@icloud.com"

                                    "CHANGETOYOURADDRESS@gmail.com"

                                    "CHANGETOYOURADDRESS@mail.harvard.edu"
                                    "CHANGETOYOURADDRESS@mail.harvard.edu"

                                    "CHANGETOYOURADDRESS@partners.org"))

;;; Default signature
(setq mu4e-compose-signature
      "YOUR NAME
YOUR AFFILIATIONS")


;;; Dynamic folder selection
;; mu4e requires to specify drafts, sent, and trash dirs (relative to mu4e-maildir)
;; Dynamic folders
;; http://www.djcbsoftware.nl/code/mu/mu4e/Dynamic-folders.html
;;
;;;  Draft
;; Put all draft in one folder (less confusing)
(setq mu4e-drafts-folder "/icloud/Drafts")
;;
;;;  Sent
;; http://www.djcbsoftware.nl/code/mu/mu4e/Saving-outgoing-messages.html
(defun my-mu4e-sent-folder-function (msg)
  "Set the sent folder for the current message."
  (let ((from-address (message-field-value "From"))
        (to-address   (message-field-value "To")))
    (cond
     ((string-match "@gmail.com" from-address)
      "/gmail/sent")
     ((string-match "@mail.harvard.edu" from-address)
      "/harvard/sent")
     ((string-match "@mac.com\\|@me.com\\|@icloud.com" from-address)
      "/icloud/Sent Messages")
     (t
      ;; (mu4e-ask-maildir-check-exists "Could not determine sent folder; Save message to maildir: ")
      ;; If unsure, just save to iCloud
      "/icloud/Sent Messages"))))
(setq mu4e-sent-folder 'my-mu4e-sent-folder-function)
;;
;; Determines what mu4e does with sent messages.
;; This is one of the symbols:
;; * ‘sent’    move the sent message to the Sent-folder (‘mu4e-sent-folder’)
;; * ‘trash’   move the sent message to the Trash-folder (‘mu4e-trash-folder’)
;; * ‘delete’  delete the sent message.
(setq mu4e-sent-messages-behavior
      (lambda ()
        (let ((from-address (message-sendmail-envelope-from)))
          (cond
           ((string= from-address "CHANGETOYOURADDRESS@gmail.com") 'delete)
           ((string= from-address "CHANGETOYOURADDRESS@mail.harvard.edu") 'delete)
           (t 'sent)))))
;;
;;;  Trash
;; http://www.gnu.org/software/emacs/manual/html_node/emacs/Mail-Headers.html
;; http://www.djcbsoftware.nl/code/mu/mu4e/Message-functions.html
(defun my-mu4e-trash-folder-function (msg)
  "Set the sent folder for the current message."
  (let* ((maildir-str (mu4e-message-field msg :maildir)))
    (message maildir-str)
    (cond
     ((string-match "/gmail/" maildir-str)
      "/gmail/trash")
     ((string-match "/harvard/" maildir-str)
      "/harvard/trash")
     ((string-match "/icloud/" maildir-str)
      "/icloud/Deleted Messages")
     ;; If unsure, use iCloud
     (t "/icloud/Deleted Messages"))))
(setq mu4e-trash-folder 'my-mu4e-trash-folder-function)
;;
;;;  Refiling
;; http://www.djcbsoftware.nl/code/mu/mu4e/Smart-refiling.html


;;; Bookmarks
;; https://www.djcbsoftware.nl/code/mu/mu4e/Searching.html
;; Many example queries
;; https://www.djcbsoftware.nl/code/mu/mu4e/Queries.html
;; https://www.djcbsoftware.nl/code/mu/cheatsheet.html
;; https://www.djcbsoftware.nl/code/mu/mu4e/Bookmarks.html
;; Modified version of default set
(let* ((not-in-sent (concat " AND NOT maildir:/icloud/Sent+Messages"
                            " AND NOT maildir:/gmail/sent"
                            " AND NOT maildir:/harvard/sent"))
       (not-in-deleted (concat " AND NOT flag:trashed"
                               " AND NOT maildir:/icloud/Deleted+Messages"
                               " AND NOT maildir:/gmail/trash"
                               " AND NOT maildir:/harvard/trash"))
       (not-in-drafts (concat " AND NOT maildir:/icloud/Drafts")))
  (print not-in-sent)
  ;;
  (setq mu4e-bookmarks
        `((,(concat "maildir:/icloud/inbox"
                    " OR maildir:/gmail/inbox"
                    " OR maildir:/harvard/inbox")
           "Common INBOX" ?i)
          ;;
          (,(concat "maildir:/icloud/Sent+Messages"
                    " OR maildir:/gmail/sent"
                    " OR maildir:/harvard/sent")
           "Common Sent" ?s)
          ;;
          (,(concat "maildir:/icloud/Deleted+Messages"
                    " OR maildir:/gmail/trash"
                    " OR maildir:/harvard/trash")
           "Common Trash" ?T)
          ;;
          (,(concat "maildir:/icloud/Drafts")
           "Common Drafts" ?d)
          ;;
          (,(concat "flag:unread"
                    not-in-sent not-in-deleted not-in-drafts)
           "Unread messages" ?u)
          ;;
          (,(concat "date:today..now"
                    not-in-sent not-in-deleted not-in-drafts)
           "Today's messages" ?t)
          ;;
          (,(concat "date:7d..now"
                    not-in-sent not-in-deleted not-in-drafts)
           "Last 7 days" ?w)
          ;;
          (,(concat "flag:attach"
                    not-in-sent not-in-deleted not-in-drafts)
           "Attachements" ?a)
          ;;
          (,(concat "size:5M..500M"
                    not-in-sent not-in-deleted not-in-drafts)
           "Big messages" ?b))))

imapnotify

mbsyncにはIMAP IDLE機能がないので、IMAP IDLEでinboxを見張って適宜mbsyncを呼びます。ここではinboxのみの同期の設定で呼びだしています。アカウントごとに設定ファイルが必要です。パスワードは前述のmbsync用に構築したKeychainがなぜかそのまま使えます(App-specificなのに)。それぞれの設定ファイルで起動する必要がありますが、これはprodigy.elを使ってemacs内から管理します。IMAP IDLEで新規メールを認識すると、mbsyncでメールを取り寄せ、さらにemacsclientごしにmu4eとローカルメールボックスの同期を行うという、仕組みのようです。

個人Gmail: ~/.imapnotify_gmail.js

// imapnotify configuration
// Handling Email with Emacs
// https://martinralbrecht.wordpress.com/2016/05/30/handling-email-with-emacs/
// npm: imapnotify Execute scripts on new messages using IDLE imap command
// https://www.npmjs.com/package/imapnotify
// Execute scripts on new messages using IDLE imap command
// https://github.com/a-sk/node-imapnotify
// Install Node.js and npm using Homebrew on OS X and macOS
// https://changelog.com/posts/install-node-js-with-homebrew-on-os-x
// dtrace-provider - Native DTrace providers for Node.js apps.
// https://github.com/chrisa/node-dtrace-provider

// Installation on macOS
// brew install node
// npm init
// npm install imapnotify --no-optional (to avoid dtrace-provider issue)

var child_process = require('child_process');

function getStdout(cmd) {
    var stdout = child_process.execSync(cmd);
    return stdout.toString().trim();
}

exports.host = "imap.gmail.com";
exports.port = 993;
exports.tls = true;
exports.tlsOptions = { "rejectUnauthorized": false };
exports.username = "CHANGETOYOURADDRESSgmail.com";
exports.password = getStdout("security find-generic-password -s mbsync-gmail -w");
exports.onNewMail = "timelimit -t 120 mbsync inbox-only";
exports.onNewMailPost = "emacsclient -e '(mu4e-update-index)'";
exports.boxes = [ "INBOX"];

大学院Gmail: ~/.imapnotify_harvard.js

var child_process = require('child_process');

function getStdout(cmd) {
    var stdout = child_process.execSync(cmd);
    return stdout.toString().trim();
}

exports.host = "imap.gmail.com";
exports.port = 993;
exports.tls = true;
exports.tlsOptions = { "rejectUnauthorized": false };
exports.username = "CHANGETOYOURADDRESS@mail.harvard.edu";
exports.password = getStdout("security find-generic-password -s mbsync-harvard -w");
exports.onNewMail = "timelimit -t 120 mbsync inbox-only";
exports.onNewMailPost = "emacsclient -e '(mu4e-update-index)'";
exports.boxes = [ "INBOX"];

iCloud: ~/.imapnotify_icloud.js

var child_process = require('child_process');

function getStdout(cmd) {
    var stdout = child_process.execSync(cmd);
    return stdout.toString().trim();
}

exports.host = "imap.mail.me.com";
exports.port = 993;
exports.tls = true;
exports.tlsOptions = { "rejectUnauthorized": false };
exports.username = "CHANGETOYOURADDRESS@mac.com";
exports.password = getStdout("security find-generic-password -s mbsync-icloud -w");
exports.onNewMail = "timelimit -t 120 mbsync inbox-only";
exports.onNewMailPost = "emacsclient -e '(mu4e-update-index)'";
exports.boxes = [ "INBOX"];

prodigy

prodigy.elは特定の設定で外部サービスを起動したいときに便利なemacsパッケージです。サービスという設定オブジェクトを定義してそれをコントロールパネルから起動したり停止したりできるようになります。ここでは面倒なので、 after-init-hook でemacs起動後に、自動的に三つのアカウントにそれぞれimapnotifyを起動するように設定しています。 M-x prodigy でコントールパネルを開くと起動状況、サービス名で$を叩くとでログが見られます。

;;;
;;; prodigy.el
;; https://github.com/rejeep/prodigy.el
;; Handling Email with Emacs
;; https://martinralbrecht.wordpress.com/2016/05/30/handling-email-with-emacs/
(use-package prodigy
  :commands (prodigy
             prodigy-start-service
             prodigy-find-service)
  :init
  ;; After initialization, start these services
  (add-hook 'after-init-hook #'(lambda ()
                                 ;; prodigy-find-service obtains a property list object by name
                                 (prodigy-start-service
                                     (prodigy-find-service "imapnotify-gmail"))
                                 (prodigy-start-service
                                     (prodigy-find-service "imapnotify-harvard"))
                                 (prodigy-start-service
                                     (prodigy-find-service "imapnotify-icloud"))))
  :config
  ;; Tag
  ;; Define a new tag with ARGS.
  (prodigy-define-tag
    :name 'email
    :ready-message "Checking Email using IMAP IDLE. Ctrl-C to shutdown.")
  ;; Service
  ;; Define a new service with ARGS.
  (prodigy-define-service
    :name "imapnotify-gmail"
    :command "~/node_modules/imapnotify/bin/imapnotify"
    :args (list "-c" (expand-file-name ".imapnotify_gmail.js" (getenv "HOME")))
    :tags '(email)
    :kill-signal 'sigkill)
  (prodigy-define-service
    :name "imapnotify-harvard"
    :command "~/node_modules/imapnotify/bin/imapnotify"
    :args (list "-c" (expand-file-name ".imapnotify_harvard.js" (getenv "HOME")))
    :tags '(email)
    :kill-signal 'sigkill)
  (prodigy-define-service
    :name "imapnotify-icloud"
    :command "~/node_modules/imapnotify/bin/imapnotify"
    :args (list "-c" (expand-file-name ".imapnotify_icloud.js" (getenv "HOME")))
    :tags '(email)
    :kill-signal 'sigkill))

参考資料

設定ファイルに適宜参考にしたURLを含めているので、細かい点はそちらを参照してください。一番役立ったものを下記に列記します。

備考

この文章の作成にはorg-modeからQiita形式のMarkdownを出力する ox-qmd を使用しました。

この投稿は Emacs Advent Calendar 201619日目の記事です。
Comments Loading...