https://img.alicdn.com/imgextra/i2/581166664/O1CN01Zmz1yL1z6A2v1sKsI_!!581166664.jpg
Emacs as Email client

对于程序员来说,除了编写代码外,阅读邮件与技术文章是耗时比较久的事情,更重要的一点是,这两个的信息输入量都比较大,会涉及到做笔记、TODO 等,一般来说这些事情都是分散在不同应用解决的,但对 Emacs 用户来说,自然希望能在 Emacs 里做所有事

这篇文章就给读者分享下 Emacs 作为邮件、RSS 客户端的使用方式与心得。在介绍具体配置前,先展示下使用效果(主题使用 gruvbox),以便读者欣赏。

https://img.alicdn.com/imgextra/i2/581166664/O1CN01QsaGky1z6A2n7bWaU_!!581166664.jpg
mu4e 主界面
https://img.alicdn.com/imgextra/i2/581166664/O1CN01EO1Wuq1z6A2u8UTWq_!!581166664.png
mu4e 邮件列表,默认以话题归类
https://img.alicdn.com/imgextra/i3/581166664/O1CN01oIAjXl1z6A2wBzcu4_!!581166664.png
mu4e 浏览邮件
https://img.alicdn.com/imgextra/i2/581166664/O1CN01acjyqM1z6A2n2kQdU_!!581166664.png
mu4e 回复邮件
https://img.alicdn.com/imgextra/i4/581166664/O1CN014tvhqR1z6A2unN0Md_!!581166664.png
elfeed-dashborad 主界面
https://img.alicdn.com/imgextra/i2/581166664/O1CN01Sgm5pt1z6A2pZSOSa_!!581166664.png
elfeed 文章列表
https://img.alicdn.com/imgextra/i2/581166664/O1CN01VfB4Is1z6A2u9SUjw_!!581166664.png
elfeed 浏览文章

邮件

阅读邮件我使用的软件主要有

  • offlineimap 使用 imap 协议,以 Maildir 格式来同步邮件;类似的软件有 Mbsync

  • mu4e mu 的 Emacs 插件,mu 可以对 Maildir 格式的邮件建索引,便于搜索;类似的软件有 Gnus

offlineimap 使用 Python 编写,在 macOS 直接使用 brew 即可安装。下面是我目前的配置文件(配置文件推荐放在 ~/.config/offlineimap/config ,尽量避免直接在家目录创建 ~/.offlineimaprc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[general]
# 默认开启的账户,可以是多个
accounts = ljc, outlook
# 定义额外的 python 脚本,里面有获取密码的函数
pythonfile = ~/.config/offlineimap/pass.py

# 开始配置第一个账户
[Account ljc]
# 一个账户主要包含 local 与 remote 这两个配置项
localrepository = LocalLJC
remoterepository = RemoteLJC
# 可选配置项,邮件同步时间:这里只同步 365 天之内的
# 需要注意,一些邮件提供商也存在同步限制,比如 QQ 邮箱默认只同步 30 天内的数据,可以在设置中修改
maxage = 365

[Repository LocalLJC]
type = Maildir
localfolders = ~/.mail/ljc
# 需要与 RemoteLJC 里面的 nametrans 配置起来使用,解释见正文
nametrans = lambda folder: re.sub('^=', '&UXZO1mWHTvZZOQ-/', folder)

[Repository RemoteLJC]
nametrans = lambda folder: re.sub('^&UXZO1mWHTvZZOQ-\/', '=', folder)
# 配置 ssl 证书,macOS 下安装 openssl 即可
sslcacertfile = /usr/local/etc/openssl@1.1/cert.pem
# 过滤掉一些目录,不进行同步
folderfilter = lambda f: 'kK5O9l9SaGM' not in f and '&UXZO1mWHTvZZOQ-' != f
# 下面是 IMAP 的配置,我这里是使用的腾讯企业邮箱
type = IMAP
remotehost = imap.exmail.qq.com
remoteport = 993
remoteuser = hello@example.com
# 调用 pass.py 中的函数获取密码
remotepasseval = get_password_emacs("imap.exmail.qq.com", "hello@example.com", "993")
# 也可通过下面配置直接设置密码
# remotepass = YOUR_MAIL_PASSWORD
[Account outlook]
... 大致同上,这里不在赘述

上面的配置比较直观,也有相应注释,这里重点介绍如何设置 remotepassevalnametrans

GPG Auth Source 配置密码

Emacs 中使用 Auth Source 来管理密码,它相当于一个接口层,可以对接多个存储后端,netrc 是最常见的后端,除此之外,还支持 JSON、Secret Service API、pass。很多命令,比如 wgetcurlgit 等都支持从 netrc 读取密码,因为这里只介绍其用法:

machine mymachine login myloginname password mypassword port myport

上面描述了 netrc 文件的通用格式,下面是我邮箱的相关配置:

# cat ~/.authinfo
machine imap.exmail.qq.com login hello@example.com password 123456 port 993
machine smtp.exmail.qq.com login hello@example.com password 123456 port 465

993 是 imap 端口,接受邮件用;465 是 smtp 端口,发送邮件用。为了安全,还可以对 netrc 使用 GPG 加密,Emacs 会自动解密读取。

(setq auth-sources '("~/.authinfo.gpg" "~/.authinfo" "~/.netrc"))

Emacs 默认从上面几个地方找文件,读者可按需修改。

我上面对配置中使用了 get_password_emacs 这个函数来获取密码,其实现如下:

1
2
3
4
5
6
def get_password_emacs(machine, login, port):
    s = "machine %s login %s password ([^ ]*) port %s\n" % (
        machine, login, port)
    p = re.compile(s)
    authinfo = os.popen("gpg -q --no-tty -d ~/.config/authinfo.gpg").read()
    return p.search(authinfo).group(1)

nametrans 文件名转换

配置好密码后,就可以进行邮件同步了。但是在同步前,可以通过 offlineimap --info 命令查看邮件服务器中的内容,确认下需要同步哪些目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ offlineimap --info
OfflineIMAP 7.3.3
  Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception)
imaplib2 v2.101 (bundled), Python v2.7.17, OpenSSL 1.1.1i  8 Dec 2020
  imaplib2: 2.101 (bundled)
Remote repository 'RemoteLJC': type 'IMAP'
Host: imap.exmail.qq.com Port: None SSL: True
Establishing connection to imap.exmail.qq.com:993 (RemoteLJC)
Server supports ID extension.
Server welcome string: * OK [CAPABILITY IMAP4 IMAP4rev1 ID AUTH=PLAIN AUTH=LOGIN NAMESPACE] QQMail IMAP4Server ready
Server capabilities: ('IMAP4', 'IMAP4REV1', 'XLIST', 'MOVE', 'IDLE', 'XAPPLEPUSHSERVICE', 'NAMESPACE', 'CHILDREN', 'ID', 'UIDPLUS')

folderfilter= lambda f: 'kK5O9l9SaGM' not in f and '&UXZO1mWHTvZZOQ-' != f

nametrans= lambda folder: re.sub('^&UXZO1mWHTvZZOQ-\/', '=', folder)

Folderlist:
 &UXZO1mWHTvZZOQ- (disabled)
 &UXZO1mWHTvZZOQ-/clojure -> =clojure
 &UXZO1mWHTvZZOQ-/GitHub -> =GitHub
 &UXZO1mWHTvZZOQ-/golang -> =golang
 Deleted Messages
 Drafts
 INBOX
 Junk
 Sent Messages

在 Folderlist 下面就是邮件服务器中的目录, &UXZO1mWHTvZZOQ- 开头的目录为自定义目录,为了保证同步下来的目录中没有这一串字母,我使用 nametrans 配置把它转化为了 = 符号。

由于 IMAP 的同步是双向的,本地的状态也需要返回给远端,因此在 LocalLJC 中又使用 nametrans= 转成了那串字母,这样两边就能正确同步邮件状态了,下面是本地的状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Local repository 'LocalLJC': type 'Maildir'
nametrans= lambda folder: re.sub('^=', '&UXZO1mWHTvZZOQ-/', folder)

Folderlist:
 INBOX
 Drafts
 Junk
 =GitHub -> &UXZO1mWHTvZZOQ-/GitHub
 Deleted Messages
 =golang -> &UXZO1mWHTvZZOQ-/golang
 =clojure -> &UXZO1mWHTvZZOQ-/clojure
 Sent Messages

最后,直接运行 offlineimap 就可以同步邮件了。

mu4e

邮件同步到本地后,在使用前需要使用 mu 建索引,参考命令:

1
2
3
4
5
export XAPIAN_CJK_NGRAM=true
# 只需要执行一次 init,可以指定多个邮件地址
mu init --my-address your-mail@qq.com --my-address your-mail@gmail.com -m ~/.mail
# index 在每次收取邮件后都需要执行,mu4e 可以配置自动执行
mu index

XAPIAN_CJK_NGRAM 环境变量主要是开启对 CJK 的分词,方便用中文搜索邮件。不过 mu 使用的 Xapian 对中文支持比较弱,只能搜索两个字的词,比如搜「雷峰塔」就不行,可参考这里的解决方案。

配置

mu4e 在 mu 的安装目录内,不需要再额外安装。除 mu4e 外,推荐安装下面这几个 Emacs 插件

  • maildirs,它可以把每个目录邮件数展示出来。mu4e 相关配置可以在

  • 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 主要有两种使用方式

    1. 在一个 org 文件中调用 org-mime-org-buffer-htmlize (推荐绑定到 C-c C-c ),这会自动把当前 org 文件转为 html 并打开编辑邮件的 buffer

    2. 在编辑邮件的 buffer 中,调用 org-mime-edit-mail-in-org-mode (推荐绑定到 C-c ' )打开 org-mode 编辑区,编辑好后调用 org-mime-htmlize (推荐放到 message-send-hook 里面)将 org 格式转为 html 邮件

快捷键

配置好后,直接运行 M-x mu4e 就能打开主视图 mu4e-main-view ,通过快捷键来在不同视图下切换(图片来源):

https://img.alicdn.com/imgextra/i1/581166664/O1CN01wFq1Z31z6A2t65MJh_!!581166664.png
mu4e 主要视图交互流程

下面是我在不同视图下,常用的一些快捷键:

main
  • j 跳转目录

  • b 跳转书签

  • U 收取邮件与重建索引

view
  • g 访问当前邮件中的 URL

  • a Action 选项

  • h html/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 的邮件

说明:mu 使用 https://emacs-china.org/t/topic/305/77?u=jiacai2050

RSS

阅读 RSS 使用的是 elfeed,它的配置相比邮件就简单多了,虽然 elfeed 可以不依赖外部软件执行,但是其作者还是推荐使用 curl 而非 ELisp 中的 url-retrieve,一方面是速度更快,另一方面是更稳定。除了 elfeed,还推荐安装下面两个插件:

下面阐述 elfeed 核心配置,具体配置可参考这里

  • elfeed-curl-extra-arguments 可以配置 curl 的参数,比较常用的是配置代理

  • elfeed 默认使用 shr 展示,在加载图片是会卡住,可通过设置 shr-inhibit-imagesnil 来禁用图片,之后通过自定义函数 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 重新输入搜索词

  • y yank 当前行文章的 URL,并去掉 unread 标签

  • b 调用外部浏览器打开所选行文章

  • g 刷新当前 feed 列表

  • G 重新抓取 feed 记录

elfeed-show

  • TAB 切换到下一个超链接处

  • g 刷新

  • b 调用外部浏览器打开文章

笔记

通过上面的配置,已经可以很好地在 Emacs 中阅读邮件与文章,还剩下如何记笔记没有介绍,对于 Emacser 来说,org-mode 是笔记的第一首选,而且 mu4e/elfeed 都支持 org-capture,直接在当前阅读的邮件或文章中执行就可以了。

总结

通过把邮件与 RSS 整合进 Emacs,可以充分进行沉浸式阅读,不再需要什么额外的软件,所有操作都在 Emacs 内完成,这是之前从未有过的体验。这可能也是 Emacs 自由、开放的魅力所在。

1
M-x Happy Reading