本文不再更新,最新教程会同步在:
对于程序员来说,除了编写代码外,阅读邮件与技术文章是耗时比较久的事情,更重要的一点是,这两个的信息输入量都比较大,会涉及到做笔记、TODO 等,一般来说这些事情都是分散在不同应用解决的,但对 Emacs 用户来说,自然希望能在 Emacs 里做所有事。
这篇文章就给读者分享下 Emacs 作为邮件、RSS 客户端的使用方式与心得。在介绍具体配置前,先展示下使用效果(主题使用 gruvbox),以便读者欣赏。本文中用到的配置可以在这里找到。







邮件
阅读邮件我使用的软件主要有
- offlineimap 使用 imap 协议,以 Maildir 格式来同步邮件;类似的软件有 Mbsync
- mu4e mu 的 Emacs 插件,mu 可以对 Maildir 格式的邮件建索引,便于搜索;类似的软件有 Gnus
在 macOS 上,这两个软件都可以直接使用 brew 安装。
1brew install offlineimap mu
2# mu4e 在 mu 的安装目录内,可以通过 brew ls mu 查看offlineimap
下面首先介绍 offlineimap 的配置:(配置文件推荐放在 ~/.config/offlineimap/config ,尽量避免直接在家目录创建 ~/.offlineimaprc )
1[general]
2# 默认开启的账户,可以是多个
3accounts = ljc, outlook
4# 定义额外的 python 脚本,里面有获取密码的函数
5pythonfile = ~/.config/offlineimap/pass.py
6
7# 开始配置第一个账户
8[Account ljc]
9# 一个账户主要包含 local 与 remote 这两个配置项
10localrepository = LocalLJC
11remoterepository = RemoteLJC
12# 可选配置项,邮件同步时间:这里只同步 365 天之内的
13# 需要注意,一些邮件提供商也存在同步限制,比如 QQ 邮箱默认只同步 30 天内的数据,可以在设置中修改
14maxage = 365
15
16[Repository LocalLJC]
17type = Maildir
18localfolders = ~/.mail/ljc
19# 需要与 RemoteLJC 里面的 nametrans 配置起来使用,解释见正文
20nametrans = lambda folder: re.sub('^=', '&UXZO1mWHTvZZOQ-/', folder)
21
22[Repository RemoteLJC]
23nametrans = lambda folder: re.sub('^&UXZO1mWHTvZZOQ-\/', '=', folder)
24# 配置 ssl 证书,macOS 下安装 openssl 即可
25sslcacertfile = /usr/local/etc/[email protected]/cert.pem
26# 过滤掉一些目录,不进行同步
27folderfilter = lambda f: 'kK5O9l9SaGM' not in f and '&UXZO1mWHTvZZOQ-' != f
28# 下面是 IMAP 的配置,我这里是使用的腾讯企业邮箱
29type = IMAP
30remotehost = imap.exmail.qq.com
31remoteport = 993
32remoteuser = [email protected]
33# 调用 pass.py 中的函数获取密码
34remotepasseval = get_password_emacs("imap.exmail.qq.com", "[email protected]", "993")
35# 也可通过下面配置直接设置密码
36# remotepass = YOUR_MAIL_PASSWORD
37[Account outlook]
38... 大致同上,这里不在赘述上面的配置比较直观,也有相应注释,这里重点介绍如何设置 remotepasseval 与 nametrans 。
GPG Auth Source 配置密码
Emacs 中使用 Auth Source 来管理密码,它相当于一个接口层,可以对接多个存储后端,netrc 是最常见的后端,除此之外,还支持 JSON、Secret Service API、pass。很多命令,比如 wget、curl、git 等都支持从 netrc 读取密码,因此这里只介绍它的用法:
machine mymachine login myloginname password mypassword port myport
上面是 netrc 文件的通用格式,下面是我邮箱的相关配置:
# cat ~/.authinfo machine imap.exmail.qq.com login [email protected] password 123456 port 993 machine smtp.exmail.qq.com login [email protected] password 123456 port 465
993 是 imap 端口,接受邮件用;465 是 smtp 端口,发送邮件用。为了安全,还可以对 netrc 使用 GPG 加密,Emacs 会自动解密读取。
(setq auth-sources '("~/.authinfo.gpg" "~/.authinfo" "~/.netrc"))
Emacs 默认从上面几个地方找文件,读者可按需修改。
我上面对配置中使用了 get_password_emacs 这个函数来获取密码,其实现如下:
1def get_password_emacs(machine, login, port):
2 s = "machine %s login %s password ([^ ]*) port %s\n" % (
3 machine, login, port)
4 p = re.compile(s)
5 authinfo = os.popen("gpg -q --no-tty -d ~/.config/authinfo.gpg").read()
6 return p.search(authinfo).group(1)nametrans 文件名转换
配置好密码后,就可以进行邮件同步了。但是在同步前,可以通过 offlineimap --info 命令查看邮件服务器中的内容,确认下需要同步哪些目录
1$ offlineimap --info
2OfflineIMAP 7.3.3
3 Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception)
4imaplib2 v2.101 (bundled), Python v2.7.17, OpenSSL 1.1.1i 8 Dec 2020
5 imaplib2: 2.101 (bundled)
6Remote repository 'RemoteLJC': type 'IMAP'
7Host: imap.exmail.qq.com Port: None SSL: True
8Establishing connection to imap.exmail.qq.com:993 (RemoteLJC)
9Server supports ID extension.
10Server welcome string: * OK [CAPABILITY IMAP4 IMAP4rev1 ID AUTH=PLAIN AUTH=LOGIN NAMESPACE] QQMail IMAP4Server ready
11Server capabilities: ('IMAP4', 'IMAP4REV1', 'XLIST', 'MOVE', 'IDLE', 'XAPPLEPUSHSERVICE', 'NAMESPACE', 'CHILDREN', 'ID', 'UIDPLUS')
12
13folderfilter= lambda f: 'kK5O9l9SaGM' not in f and '&UXZO1mWHTvZZOQ-' != f
14
15nametrans= lambda folder: re.sub('^&UXZO1mWHTvZZOQ-\/', '=', folder)
16
17Folderlist:
18 &UXZO1mWHTvZZOQ- (disabled)
19 &UXZO1mWHTvZZOQ-/clojure -> =clojure
20 &UXZO1mWHTvZZOQ-/GitHub -> =GitHub
21 &UXZO1mWHTvZZOQ-/golang -> =golang
22 Deleted Messages
23 Drafts
24 INBOX
25 Junk
26 Sent Messages在 Folderlist 下面就是需要同步的目录,腾讯企业邮箱比较奇葩,自定义文件夹前会有一串字母 &UXZO1mWHTvZZOQ- ,为了保证同步下来的目录中没有这一串字母,这里使用 nametrans 配置把它转化为了 = 符号。其他邮箱(比如:Gmail、Yandex)的目录都是比较规整的,这时则不需要设置 namestrans 。
由于 IMAP 的同步是双向的,本地的状态也需要返回给远端,因此在 LocalLJC 中又使用 nametrans 把 = 转成了那串字母,这样两边就能正确同步邮件状态了,下面是本地的状态:
1Local repository 'LocalLJC': type 'Maildir'
2nametrans= lambda folder: re.sub('^=', '&UXZO1mWHTvZZOQ-/', folder)
3
4Folderlist:
5 INBOX
6 Drafts
7 Junk
8 =GitHub -> &UXZO1mWHTvZZOQ-/GitHub
9 Deleted Messages
10 =golang -> &UXZO1mWHTvZZOQ-/golang
11 =clojure -> &UXZO1mWHTvZZOQ-/clojure
12 Sent Messages确认好目录同步无误后,直接运行 offlineimap 就可以同步邮件了。
mu4e
邮件同步到本地后,在使用前需要使用 mu 建索引,参考命令:
1export XAPIAN_CJK_NGRAM=true
2# 只需要执行一次 init,可以指定多个邮件地址
3mu init --my-address [email protected] --my-address [email protected] -m ~/.mail
4# index 在每次收取邮件后都需要执行,mu4e 可以配置自动执行
5mu indexXAPIAN_CJK_NGRAM 环境变量主要是开启对 CJK 的分词,方便用中文搜索邮件。不过 mu 使用的 Xapian 对中文支持比较弱,只能搜索两个字的词,比如搜「雷峰塔」就不行,可参考这里的解决方案。
配置
除 mu4e 外,推荐安装下面这几个 Emacs 插件
,使用一段时间后发现 maildirs 更新邮件数特别慢,比较影响用户体验,可以直接定义 bookmarks 来定义需要重点关注的文件夹maildirs,它可以把每个目录邮件数展示出来
maildirs 更新数字太慢示意图 - org-mime,它可以把 org-mode 格式转为 HTML 的邮件
- visual-fill-column,相当于 fill-column 与 visual-line-mode 的结合版,主要方便在大屏显示器上阅读,效果是把长行拆分是多行。
核心配置主要有以下几个:
mu4e-contexts配置多账户mu4e-update-interval配置自动同步邮件与建索引间隔mu4e-get-mail-command配置同步邮件的命令mu4e-view-actions增加mu4e-action-view-in-browser,这样在阅读邮件时,按a b在浏览器打开邮件,这对那些只有 HTML 格式的邮件来说比较重要message-citation-line-format定义符合 Gmail 的引用格式,Gmail 会用...将引用隐藏起来mu4e-maildirs-extension-custom-list配置目录展示顺序org-mime 主要有两种使用方式
- 在一个 org 文件中调用
org-mime-org-buffer-htmlize(推荐绑定到C-c C-c),这会自动把当前 org 文件转为 html 并打开编辑邮件的 buffer - 在编辑邮件的 buffer 中,调用
org-mime-edit-mail-in-org-mode(推荐绑定到C-c ')打开 org-mode 编辑区,编辑好后调用org-mime-htmlize(推荐放到message-send-hook里面)将 org 格式转为 html 邮件
- 在一个 org 文件中调用
快捷键
配置好后,直接运行 M-x mu4e 就能打开主视图 mu4e-main-view ,通过快捷键来在不同视图下切换(图片来源):

下面是我在不同视图下,常用的一些快捷键:
main
j跳转目录b跳转书签U收取邮件与重建索引
view
g访问当前邮件中的 URLaAction 选项hhtml/text 格式切换.查看邮件源文件A附件选项C写新邮件R回复邮件
compose
C-c C-c发送C-c C-k废弃C-c C-d存草稿C-c C-a添加附件
Flags 含义
| u | unread |
| S | Seen |
| D | Draft |
| A | has-attachment |
| F | Flagged |
| N | New |
| P | Passed/Forwarded |
| R | Replied |
| T | Trashed |
| x | encrypted |
| s | signed |
查询语法
| 查询语句 | 含义 |
|---|---|
date:today..now | 今天的邮件 |
from:jim and not flag:attach | 来自 jim 且没有附件的邮件 |
"maildir:/Sent Items" and rupert | 发送目录内,包含 rupert 的邮件 |
subject:wombat and date:20170601..20170630 | 指定时间内,主题中包含 wombat 的邮件 |
系统默认客户端
在网页中点击含有 mailto:[email protected] 的链接时,会调用操作系统默认邮件客户端来处理,这里介绍一种方式来把 mu4e 设置为 macOS 上的默认客户端。
首先把 mu4e 设置为 Emacs 中的默认客户端
1(setq mail-user-agent 'mu4e-user-agent)打开 Script Editor,输入以下代码后,导出为「应用」
1on open location myurl 2 set text item delimiters to {":"} 3 display notification text item 2 of myurl with title "Emacs Compose" 4 do shell script "/usr/local/bin/emacsclient -c -n --eval '(browse-url-mail \"" & myurl & "\")'" 5end open location上面代码中,
-c表示创建一个新 frame,-n表示不等待 Emacs Server 的返回,不加这个选项的话,执行这个应用时,会一直驻留在 Dock 内,处于假死的状态。
创建 Script 应用 打开 Mail.app 应用,进入设置,将上面创建的应用设置为默认邮件阅读器

在 Mail 中修改默认阅读器
完成上面三步操作配置就好了,之后点击含有 mailto 的链接时,会自动在当前 active 的 buffer 中打开 mu4e-compose 界面,同时右上角会跳出提示框。

RSS
阅读 RSS 使用的是 elfeed,它的配置相比邮件就简单多了,虽然 elfeed 可以不依赖外部软件执行,但是其作者还是推荐使用 curl 而非 ELisp 中的 url-retrieve,一方面是速度更快,另一方面是更稳定。除了 elfeed,还推荐安装下面两个插件:
- elfeed-org 使用 org 文件来定义 feeds 列表,它可以很方便进行打标签,重命名
- elfeed-dashboard 把定义的 feeds 列表按标签展示
下面阐述 elfeed 核心配置:
elfeed-curl-extra-arguments可以配置 curl 的参数,比较常用的是配置代理- elfeed 默认使用 shr 展示,在加载图片是会卡住,可通过设置
shr-inhibit-images为nil来禁用图片,之后通过自定义函数my/show-feed-images来开启图片 - 为了方便记录重要文章,定义
my/elfeed-toggle-star函数来实现「标星」效果,并可以使用+starred来搜索 - 默认 elfeed 列表行间距太小,可以通过
(setq-local line-spacing 0.3)调整
查询语法
| 查询语句 | 含义 |
|---|---|
@6-months-ago +unread | 6 个月内的未读文章 |
-unread +youtube #10 | 已读的与 youtube 相关的前 10 篇文章 |
+emacs =http://example.org/feed | 指定 feed 内,与 emacs 有关的文章 |
常用快捷键
elfeed-search
s在 minibuffer 重新输入搜索词yyank 当前行文章的 URL,并去掉 unread 标签b调用外部浏览器打开所选行文章g刷新当前 feed 列表G重新抓取 feed 记录
elfeed-show
TAB切换到下一个超链接处g刷新b调用外部浏览器打开文章
导入/导出
elfeed 的数据库存放在本地,可以使用一些云盘来进行数据。此外,我实现了两个函数,来对指定的 tag 进行导出/导入,这样相当于只备份元数据。
导出时主要有两个 tag,一个是 +starred ,表示加星的文章;另一个是 -unread ,表示已读的。
1(defun my/elfeed-export (output)
2 (interactive "fOutput: ")
3 (let* ((sf (elfeed-search-parse-filter "+starred"))
4 (uf (elfeed-search-parse-filter "-unread"))
5 (starred-entries '())
6 (read-entries '())
7 (hash-table (make-hash-table)))
8 (with-elfeed-db-visit (entry feed)
9 (when (elfeed-search-filter sf entry feed)
10 (add-to-list 'starred-entries (elfeed-entry-link entry)))
11 (when (elfeed-search-filter uf entry feed)
12 (add-to-list 'read-entries (elfeed-entry-link entry))))
13
14 (puthash :starred starred-entries hash-table)
15 (puthash :read read-entries hash-table)
16 (f-write-text (prin1-to-string hash-table) 'utf-8 output)
17
18 (message "Export to %s. starred: %d, read: %d" output (length starred-entries) (length read-entries))))
19
20(defun my/elfeed-import (f)
21 (interactive "fInput: ")
22 (let* ((hash-table (read (f-read-text f)))
23 (starred-entries (gethash :starred hash-table))
24 (read-entries (gethash :read hash-table)))
25 (with-elfeed-db-visit (entry feed)
26 (let* ((link (elfeed-entry-link entry)))
27 (when (member link starred-entries)
28 (elfeed-tag entry (intern "starred")))
29 (when (member link read-entries)
30 (elfeed-untag entry (intern "unread")))))
31
32 (message "Import starred: %d, read: %d" (length starred-entries) (length read-entries))))笔记
通过上面的配置,已经可以很好地在 Emacs 中阅读邮件与文章,还剩下如何记笔记没有介绍,对于 Emacser 来说,org-mode 是笔记的第一首选,而且 mu4e/elfeed 都支持 org-capture,直接在当前阅读的邮件或文章中执行就可以了。
总结
通过把邮件与 RSS 整合进 Emacs,可以充分进行沉浸式阅读,不再需要什么额外的软件,所有操作都在 Emacs 内完成,这是之前从未有过的体验。这可能也是 Emacs 自由、开放的魅力所在。
1M-x Happy Reading