为什么要用 Mutt
Mutt 是一个运行在终端中的邮件客户端,基本上对于它的第一印象就是麻烦以及不够直观,当然我最开始也是拒绝的,无奈 Linux 下也确实没有一个令我满意的邮件客户端,结果使用了 Mutt 之后发现它其实很契合自己的需求:
- 大部分邮件只需看一眼标题然后归档即可,少部分邮件也只需要提取 URL 或者附件。
- 讨厌邮件中包含的各种 Tracker。
- 需要将所有邮件在本地备份。
- 可以通过一行命令快速的发送邮件。
- 喜欢使用纯文本格式的邮件。
方案
Mutt 有许多不同的分支,分别编译了不同的功能和补丁,现在比较主流的是 NeoMutt。由于我需要将所有邮件在本地保留备份,所以没有使用 NeoMutt 自带的 IMAP,而是使用 OfflineIMAP 将邮件与本地 Maildir 同步,存储在 Maildir 的邮件可以被 NeoMutt 直接读取以及操作。至于发送邮件方面由于没有特殊需求,所以我直接使用了 NeoMutt 自带的 SMTP 功能。
可能需要安装的包:neomutt, offlineimap 以及 lynx。
配置 OfflineIMAP
OfflineIMAP 的配置文件可以放在 ~/.config/offlineimap/config
,这里我简化了一下我的配置文件,用作抛砖引玉。
[general]
accounts = main
[Account main]
localrepository = main-local
remoterepository = main-remote
autorefresh = 0.2
quick = 10
postsynchook = ~/.config/offlineimap/notify.sh
[Repository main-local]
type = Maildir
localfolders = ~/.mail
[Repository main-remote]
type = IMAP
remoteport = 1143
remotehost = 127.0.0.1
remoteuser = username
remotepass = password
folderfilter = lambda foldername: foldername in ['INBOX', 'Archive', 'Sent', 'Trash']
# SSL
ssl = no
starttls = yes
ssl_version = tls1_3
sslcacertfile = ~/.config/offlineimap/relay.cert
其中有几处需要注意的地方:
postsynchook
后面的命令会在收到新邮件后运行,这里我运行的是我自己写的一个简单的 Shell Script,作用是检查新邮件并发送系统通知:#!/bin/bash set -Eeo pipefail MAILDIR=~/.mail/INBOX/new/ WORKDIR="${XDG_CACHE_HOME:-$HOME/.cache}" cd $WORKDIR if [[ -f notifyDB ]]; then CURRID=$(cat notifyDB) else CURRID=0 fi for FNAME in $(ls $MAILDIR); do ID=$(echo $FNAME | sed 's/_.*//') if [[ "$ID" -gt "$CURRID" ]]; then EMLFROM=$(cat $MAILDIR$FNAME | grep "^From: ") EMLSUBJ=$(cat $MAILDIR$FNAME | grep "^Subject: ") notify-send "$EMLFROM" "$EMLSUBJ" -t 5000 paplay /usr/share/sounds/Oxygen-Im-New-Mail.ogg fi echo "$ID" > notifyDB done
localfolders
是存放 Maildir 的路径,需要自己建立。remoteuser
和remotepass
这里我直接使用的明文,原因是我连接的是一个在本地运行的 IMAP Relay。一般来讲这里推荐通过一个 Password Manager 来提供密码,详情可以查看这里,或者官方配置文档中的remoteusereval
以及remotepasseval
。['INBOX', 'Archive', 'Sent', 'Trash']
这里表示同步了四个文件夹,文件夹的名字要与服务器对应起来。SSL 的配置方面我选择了关闭强制 SSL 并开启了 STARTTLS,版本只开启了 TLS1.3,具体请根据自己的邮件供应商自行选择。
sslcacertfile
指向 IMAP 需要用到的证书,一般指向CA Certificates
所在的位置(不同发行版存放的位置不同)即可。由于我运行在本地的 IMAP Relay 使用的是自签名证书,所以直接选择自己抓取:openssl s_client -starttls imap -connect 127.0.0.1:1143 </dev/null 2>/dev/null | openssl x509 -text > ~/.config/offlineimap/relay.cert
配置好后使用 offlineimap
命令即可运行 OfflineIMAP,可以先运行一下来查看配置是否正确,确认配置没问题后即可设置后台自动同步,我选择的是用 Systemd 来管理:
systemctl --user enable --now offlineimap
配置 NeoMutt
主配置文件存放在 ~/.config/mutt/muttrc
,需要用到的其他配置文件或脚本也可以一并扔到 mutt
目录里。
由于强迫症使然,配置中只设置了与 NeoMutt 默认配置不同的地方,
配置文件中 set 代表把 xxx 设置成 yes,unset 代表把 xxx 设置成 no,而 source 的作用是引用其他文件。
基本设置
前文中我们已经通过 OfflineIMAP 将邮件同步到 Maildir (~/.mail),这里首先进行相应的设置以来让 Mutt 可以管理 Maildir。
set header_cache = ~/.cache/mutt_header_cache
set tmpdir = /dev/shm
set folder = ~/.mail
set mbox_type = Maildir
set spoolfile=+INBOX
set mbox=+Archive
set record=+Sent
set postponed=+Drafts
set trash=+Trash
alternates .+@example.com alt1@gmail.com alt2@gmail.com
mailboxes +INBOX +Archive +Sent +Drafts +Trash +Local
header_cache
顾名思义为存储邮件的 header 的缓存,可以指向文件也可以指向文件夹,设置了之后可以提升读取速度。tmpdir
为临时文件夹,撰写新邮件和读取附件时会把文件存储在这里,我直接设置成了存储到 tmpfs。folder
为 mailbox 的位置,而 mbox_type
则表示 mailbox 的类型,这里我使用的是 Maildir。spoolfile
, mbox
, record
, postponed
, trash
则分别表示了 Inbox, Archive, Sent, Drafts, Trash 这五个文件夹的位置,其中 +
展开时会变成 ~/.mail/,也就是 folder 中设置的目录,所以 spoolfile=+INBOX
代表了 spool mailbox (inbox) 存储在 ~/.mail/INBOX
。alternates
后面列举了属于用户的邮件地址,可以支持正则表达,使劲往里面填就是了,这个会影响到 NeoMutt 对邮件进行分类和标记。mailboxes
后面包含了所有 NeoMutt 需要同步的文件夹,其中 +
同样代表了 folder 中设置的目录。
杂项
set sleep_time = 0
set timeout = -1
set assumed_charset = "utf-8:GB18030"
set crypt_use_gpgme
set pgp_default_key="0xXXXXXXXXXXXXXXXX"
source ~/.config/mutt/gpgrc
unset help
unset wait_key
简单介绍一下各行的作用:sleep_time
设置成 0 的主要原因是可以让切换文件夹的时候不会卡顿。timeout
设置成了 -1,由于 NeoMutt 会在闲置时停止检查新邮件等行为,所以直接设置成不会进入闲置状态。assumed_charset
表示了当邮件里没有标明 encoding 时,会按照顺序来尝试 decode。crypt_use_gpgme
开启 GPGME 支持。pgp_default_key
直接填写自己的 PGP 公钥 fingerprint 即可。
引用的 gpgrc 是直接复制的 /usr/share/doc/neomutt/samples/gpg.rc
,具体包含的内容可以自己查看和订制一下。help
设置成 no 可以隐藏快捷键提示,这个快捷键提示着实鸡肋,总是提示不到点子上。wait_key
设置成 no 的时候,在执行外部命令时只有出错时才会提示『按任意键继续』。
SMTP
我的 SMTP 需求很简单,所以直接使用了 NeoMutt 自带的 SMTP,用户密码也直接明文存储,正常情况下推荐使用 msmtp
并配合一个 Password Manager。
set smtp_url=smtp://username:password@127.0.0.1:1025
set ssl_force_tls
set certificate_file = ~/.config/mutt/cert
certificate_file
指向 SMTP 需要用到的证书,与之前 OfflineIMAP 中的证书一样可以直接指向 CA Certificates
所在的位置。
侧栏和状态栏
set sidebar_width=15
unset help
set status_on_top
set status_format = "-%r-[%f]---[Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b?%?l? %l?]---(%s/%S)-%>-(%P)---"
NeoMutt 默认的侧栏很宽,所以设置了一下 sidebar_width
,另外侧栏默认隐藏,我用起来很舒服就没更改,如果有需求可以通过 set sidebar_visible
来设置。
而 status_on_top
可以让状态栏显示在顶端,不过需要先把快捷键提示关闭 (unset help),最后 status_format
设置了状态栏显示的内容,如果想 DIY 可以参考这里。
邮件列表
邮件列表的显示格式我更改的地方比较少,其中 date_format
与 index_format
分别表示了日期的格式以及每一个条目的格式,具体可以查看这里。sort
与 sort_aux
则设置成按照主题分类,并且将最新收到的邮件放在前面,最后设置 collapse_all
让相同主题默认折叠。
set date_format = "%m/%d"
set index_format = "%4C [%Z] %D %-15.15F %s"
set sort = threads
set sort_aux = reverse-last-date-received
set collapse_all
页面
页面 (Pager) 界面则负责管理邮件以及帮助页面的显示格式。
set pager_index_lines = 6
set pager_context = 3
set pager_stop
set tilde
unset markers
unhdr_order *
hdr_order from: to: cc: date: subject:
auto_view text/html
alternative_order text/plain text/enriched text/html
set display_filter="sed -e '/\\[-- Type: text.* --\\]/d' | sed -e '/\\[-- Autoview.* --\\]/d' | sed -e '/\\[-- Type.* --\\]/d' | sed -e '/\\[-- .*unsupported.* --\\]/d' | sed -e '/\\[-- Attachment #[0-9] --\\]/d' | sed -e 's/Attachment #[0-9]: //g' | sed '/./,/^$/!d'"
这里我设置了 pager_index_lines = 6
,可以在显示邮件内容的同时显示五条邮件的索引,因为状态栏会占用一条,所以设置成 6 的时候只会显示 5 条索引。pager_context
表示了翻页时保留的行数,而 pager_stop
则表示翻页时不会转移到下(上)一封邮件。同时我设置了 set tilde
和 unset markers
,分别代表显示空行标识符("~") 以及隐藏自动换行时标识符("+")。unhdr_order *
和 hdr_order xxx
则分别清除了默认 Header 的显示排序以及设定了新的排序。auto_view text/html
则指示 NeoMutt 自动将邮件中 text/html
部分转换为 text/plain
,这需要 mailcap
里设置 copiousoutput
,具体会在后面部分说明。最后alternative_order
表示了优先把哪一部分显示成邮件的主体。
另外单独提一下 display_filter
,其作用是对显示邮件时的内容进行过滤和替换,我这里的命令主要是让 MIME 分割线变得好看一些。
Mailcap
当 NeoMutt 遇到自己没法处理的 MIME 类型时,会把 MIME 中的内容存储在临时目录,然后按照 mailcap
里的设定来进行处理,格式是 MIME类型; 命令; copiousoutput
,命令中可以包含 %s
,表示的是临时文件夹的路径,而 copiousoutput
为可选部分,表示将命令的 output 显示在 NeoMutt 的 Pager 界面。这里以我自己的设置为例:
首先设置 mailcap 文件的位置。
set mailcap_path = ~/.config/mutt/mailcap
对于 MIME 为 text/html
的部分,使用 lynx 来处理。对于其它类型,我直接都扔给了 xdg-open
命令,不过这里有个问题,xdg-open
在判断完后即会返回,然后存储 MIME 内容的临时文件就被删除了,导致真正调用的程序无法打开文件。我使用的权宜之计是先复制一份到 tmpfs,然后再处理,因为命令很短就直接也放在 mailcap 文件里了。
text/html; lynx -assume_charset=%{charset} -display_charset=utf-8 -dump %s; nametemplate=%s.html; copiousoutput
image/*; tmpdir="/dev/shm/mutt_attach_$(whoami)" && rm -rf $tmpdir && mkdir -m 700 "$tmpdir" && cp %s $tmpdir/ && xdg-open "$tmpdir/$(basename %s)"
application/pdf; tmpdir="/dev/shm/mutt_attach_$(whoami)" && rm -rf $tmpdir && mkdir -m 700 "$tmpdir" && cp %s $tmpdir/ && xdg-open "$tmpdir/$(basename %s)"
撰写
对于撰写邮件的设置的解释我直接放在注释里了。
set realname="Coda" # 撰写新邮件时使用的名字
set from="i@coda.world" # 撰写新邮件时使用的邮件地址
set forward_format="Fwd: %s" # 转发的格式为 "Fwd: 标题"
set forward_quote # 转发时将正文放到引用中
set attribution="\n\nOn %d, %n wrote:" # 回复时引用前文的格式
set reverse_name # 优先使用收件人的身份回复,而不是使用撰写新邮件时的地址
set fast_reply # 回复时不会询问收件人和标题
set include # 回复时附上前文
set send_charset="utf-8" # 发送邮件时使用的编码
set editor="code -w -n" # 编辑器,我使用的是 vscode,'-w'代表文件关闭后再返回命令
set edit_headers # 允许编辑 Header (To: CC: Subject:)
颜色
NeoMutt 的颜色的颜色设置十分详细,不同部分的设置格式也不同,部分设置甚至支持正则表达式,具体格式可以看官方文档中 color 这部分。
建议直接 Google neomutt color scheme
来用别人做好的配色,如果想 DIY 则需要考虑不同 Terminal 中显示的颜色会有差异,可以使用下方命令来获得一个 256 色的展示。
for i in {0..255}; do printf '\e[48;5;%dm%3d ' $i $i; (((i+3) % 18)) || printf '\e[0m\n'; done
快捷键绑定
设置快捷键的方式有两种,分别是 bind
和 macro
,区别是 bind
仅能绑定一条命令,而 macro
可以绑定一系列命令。bind
的格式如下
bind view(s) key(s) function
而 macro
的格式仅仅多了个注释,并且是可选的
macro view(s) key(s) functions [description]
其中 view(s)
代表了快捷键所绑定的界面,NeoMutt 定义的界面有 alias, attach, browser, compose, editor, generic, index, mix, pager, pgp, postpone, query 和 smime,其中 generic(通用)比较特殊,当一个界面(pager 除外)没有绑定某快捷键时,会使用 generic 中绑定的快捷键作为后备,比较常用的几个界面有 index (索引), pager (页面), attach (附件) 和 compose (撰写)。key(s)
就是对应的快捷键了,例如设置成 \cD
就表示 Ctrl+D
,设置成 gi
就代表按完 (不需要按住) g
后再按 i
即可,当然这时就需要保证 g
没有绑定任何命令。
而其中 function(s)
表示执行的命令,可以设为 noop
,顾名思义表示『不作任何事情』,从而达到取消某个快捷键绑定的效果。
详细的按键列表可以看这里,而关于详细的 function 列表可以看这里。
由于强迫症的原因,我先把所有默认的 bind 和 macro 都设置成了 noop,然后又根据自己的需求重新设置了 bind 和 macro,其中 bind 由于又简单又长就不列出来了,只列一下我使用的 macro:
macro index a ":set confirmappend=no delete=yes<Return><tag-prefix><save-message>=Archive<Return><sync-mailbox>:set confirmappend=yes delete=ask-yes<Return>" "Move message to Archive mailbox"
macro index i ":set confirmappend=no delete=yes<Return><tag-prefix><save-message>=INBOX<Return><sync-mailbox>:set confirmappend=yes delete=ask-yes<Return>" "Move message to INBOX mailbox"
macro index d ":set confirmappend=no delete=yes<Return><tag-prefix><save-message>=Trash<Return><sync-mailbox>:set confirmappend=yes delete=ask-yes<Return>" "Move message to Trash mailbox"
macro pager a ":set confirmappend=no delete=yes<Return><save-message>=Archive<Return><sync-mailbox>:set confirmappend=yes delete=ask-yes<Return>" "Move message to Archive mailbox"
macro pager i ":set confirmappend=no delete=yes<Return><save-message>=INBOX<Return><sync-mailbox>:set confirmappend=yes delete=ask-yes<Return>" "Move message to INBOX mailbox"
macro pager d ":set confirmappend=no delete=yes<Return><save-message>=Trash<Return><sync-mailbox>:set confirmappend=yes delete=ask-yes<Return>" "Move message to Trash mailbox"
macro attach s "<save-entry><bol>~/Downloads/<eol><Return>" "Save attachment to ~/Downloads/"
macro compose K "<attach-key>0xXXXXXXXXXXXXXXXX<Return><Return>" "Attach my gpg key"
macro index,pager \cB "<pipe-message> urlscan<Enter>" "call urlscan to extract URLs out of a message"
macro attach,compose \cB "<pipe-entry> urlscan<Enter>" "call urlscan to extract URLs out of an attachment"
macro index,pager \` "<sync-mailbox>"
bind index,pager g noop
macro index,pager gi "<change-folder>+INBOX<Return>" "Switch to INBOX folder"
macro index,pager ga "<change-folder>+Archive<Return>" "Switch to Archive folder"
macro index,pager gs "<change-folder>+Sent<Return>" "Switch to Sent folder"
macro index,pager gd "<change-folder>+Drafts<Return>" "Switch to Drafts folder"
macro index,pager gt "<change-folder>+Trash<Return>" "Switch to Trash folder"
macro index,pager gl "<change-folder>+Local<Return>" "Switch to Local folder"
bind index,pager gv sidebar-toggle-visible
注意其中 index 和 pager 所绑定的 a、i、d 所执行的命令有着细微的差别,主要是为了可以配合其他快捷键,以便在 index 界面时允许选中多个邮件进行操作。
命令行发送邮件
echo "这是正文" | neomutt -s "这是主题" -a 附件.jpg receiver@example.com
或者直接将文件的内容作为正文发送
neomutt -s "这是主题" -a 附件.jpg receiver@example.com < 正文.txt
总结
Linux 下的邮件客户端一直是一个让我比较头疼的问题,要么功能太过简陋(例如不支持GPG),要么界面丑陋并且 Overengineering,要么闭源。而 Mutt 肯定说不上完美,只是它提供了我所需要的功能,又不会有着多余的功能(例如日历),也不会出现一堆难看的图标之类的,而且用久了会发现,这玩意儿的效率还确实不错。