~cytrogen/gemini-capsule

ref: d507759d2ed4efb6b7df81d153dc18684cc57bd7 gemini-capsule/posts/emacs-里配置邮箱服务.gmi -rw-r--r-- 7.0 KiB
d507759d — Cytrogen 使用自定义导出器重新生成所有 gmi 文件 a month ago
                                                                                
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# Emacs 里配置邮箱服务

我开始在我的笔记本电脑上使用  EndeavourOS+i3WM 以及 Emacs 了。刚开始使用 Emacs,实在是被它的能力上限所折服,巴不得所有事情都让它来做——例如邮件收发。

该文章并不是一篇教程文章,只是分享我的方案。

关于邮箱收发,我最终决定使用这样的邮件客户端流:Mu4e 客户端+mbsync。

首先需要安装以下内容:

```bash
sudo pacman -S isync msmtp msmtp-mta pass
yay -S mu
```

其中,isync 用于 IMAP 同步,mu 是邮件索引器,msmtp 是 SMTP 客户端用于发送,pass 则是密钥管理器。

配置邮件同步,需要创建 ~/.mbsyncrc :

```conf-xdefaults
Create Both
Expunge Both
SyncState *

# === Gmail ===
IMAPAccount gmail
HOST imap.gmail.com
User 邮箱地址
PassCmd "pass show email/gmail"
TLSType IMAPS
AuthMechs LOGIN

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
Path ~/Mail/Gmail/
Inbox ~/Mail/Gmail/Inbox
SubFolders Verbatim

Channel gmail
Far :gmail-remote:
Near :gmail-local:
Patterns *
```

在终端初始化目录: mkdir -p ~MailGmail 。

同步前需要生成 GPG 密钥:

```bash
gpg --full-generate-key
gpg --list-keys
pass init <GPG ID 或者最后 8 位>
pass insert email/gmail
```

最后一个命令是交互式的,需要自行粘贴邮箱的密码。对于主流的邮箱,需要输入应用密码,Gmail 和 iCloud 都是这样。

使用 mbsync gmail 来测试单个邮箱的连接。全部一起连接用 mbsync -a 。

### 初始化 mu

我们需要初始化 mu,告诉它邮件都存放在哪里、哪些是我们的邮箱地址:

```bash
mu init --maildir=~/Mail \
   --my-address=邮箱地址 \
   --my-address=另一个邮箱地址
```

最后建立索引:

```bash
mu index
```

### 将我校邮箱加进来

CUNY 用的是微软的服务,并且关闭了基本认证,必须使用 OAuth2。

我最初的方案是,弄一个脚本处理 OAuth2 握手并自动刷新令牌,也就是 mutt_oauth2.py 脚本(见 neomutt)。这个脚本需要被伪装成 Mozilla Thunderbird,来绕过应用注册审批。

```bash
mkdir -p ~/.mbsync
curl -L -o ~/.mbsync/mutt_oauth2.py https://raw.githubusercontent.com/neomutt/neomutt/master/contrib/oauth2/mutt_oauth2.py
chmod -x ~/.mbsync/mutt_oauth2.py
```

然后这么调用:

```bash
python ~/.mbsync/mutt_oauth2.py ~/.mbsync/cuny_token --verbose --authorize --provider microsoft --client-id 9e5f94bc-e8a4-4e73-b8be-63364c29d753 --authflow authcode --email <你的邮箱地址>
```

出现 Client Secret 时直接按回车,复制终端里给的 URL,最好是在无痕浏览器里登录。登录完会跳转到一个页面,赶紧复制 URL 下来,否则过几秒会跳转到警告页面。这个 URL 里有个 code 参数,复制下来(注意后面的 session_state 参数,删掉),输入到终端里……

然后你会收到 User is authorized but not connected 的消息。

为什么会这样呢?我们的 OAuth2 配置是没有问题的,但是 CUNY IT 部门把邮箱的 IMAP 和 POP 协议都禁用、不让用在应用上。

解决方法是,用 DavMail 在本地机器开启一个 IMAP 端口,mbsync 会连接到它,发送的请求会被转换成 Microsoft EWS 协议,也是 Outlook 网页版和官方客户端使用的协议。DavMail 会向微软的服务器请求数据,并伪装成 IMAP 返回给 mbsync。

创建 ~/.davmail.properties :

```conf-xdefaults
davmail.mode=O365Manual
davmail.uri=https://outlook.office365.com/EWS/Exchange.asmx
davmail.imapPort=1143
davmail.smtpPort=1025
davmail.ldapPort=1389
davmail.popPort=1110
davmail.bindAddress=127.0.0.1
davmail.oauth.clientId=d3590ed6-52b3-4102-aeff-aad2292ab01c
davmail.oauth.redirectUri=urn:ietf:wg:oauth:2.0:oob
davmail.tokenFilePath=davmail.properties
davmail.ssl.nosecurecaldav=false
log4j.rootLogger=WARN
davmail.logFilePath=/var/log/davmail.log
davmail.keepDelay=30
```

davmail.oauth.clientId 里的内容是微软 Outlook 官方客户端的 ID。
用 davmail ~/.davmail.properties 启动,它会出现一个 URL,也是建议用无痕浏览器使用。唯一的区别是,这次登录后会一直卡着,一定要在登录前打开 Inspect 的 Network 标签页,里面会有一个 302 状态码的请求,location 值里的 URL 有个 code 参数,取出来粘贴到 DavMail 里。

在另一个终端里运行 mbsync:

```bash
mbsync cuny
```

### 发送邮件

我们需要使用 msmtp 这个外部程序来发信。

如果你有着需要使用 DavMail 才能连接上的邮箱地址,msmtp 也需要通过 DavMail 发送邮件。创建 ~/.msmtprc :

```conf-xdefaults
defaults
auth            on
tls             off
tls_starttls    off
logfile         ~/.msmtp.log

account         cuny
host            127.0.0.1
port            1025
from            邮箱地址
user            邮箱地址
password        "随便写"

account         gmail
host            smtp.gmail.com
port            587
auth            on
tls             on
tls_starttls    on
user            邮箱地址
from            邮箱地址
passwordeval    "pass show email/gmail"
tls_trust_file  /etc/ssl/certs/ca-certificates.crt
```

设置文件的权限:

```bash
chmod 600 ~/.msmtprc
```

你可以这样测试发送:

```bash
echo "This is a test email from msmtp via DavMail." | msmtp --debug --account=发的账户 另一个收的邮箱
```

### 配置 Emacs

现在我们的邮件系统由三个组件协同工作:

* mbsync 负责收
* mu4e 负责界面和索引
* msmtp 负责发

我的方案是,在一个配置文件内写好所有的邮箱账号信息,例如这样:

```lisp
(:name     "CUNY"                           ; 显示名称
	   :email    "邮箱地址"
	   :fullname "blabla"                         ; 发件人名
	   :maildir  "CUNY"                           ; ~/Mail/ 下的文件夹
	   :inbox    "Inbox"                          ; 收件箱路径
	   :sent     "Sent"                           ; 已发送
	   :drafts   "Drafts"                         ; 草稿
	   :trash    "Trash"                          ; 垃圾箱
	   :archive  "Archive")                       ; 归档
```

多账号就是一个列表: (账号1 账号2 ...) 。

为了让 mu4e 支持多账号,需要写一个函数自动生成 context。

```lisp
(defun my/email--make-context (account)
  (make-mu4e-context
   :name (plist-get account :name)
   :match-func (lambda (msg)
		 (string-prefix-p "/CUNY" (mu4e-message-field msg :maildir)))
   :vars `((user-mail-address . ,(plist-get account :email))
      	   (mu4e-sent-folder . "/CUNY/Sent")
           (mu4e-drafts-folder . "/CUNY/Drafts")
  	   ...)))
```

mu4e 很大,建议用 with-eval-after-load 延迟配置:

```lisp
(with-eval-after-load 'mu4e
  (setq mu4e-maildir "~/Mail")
  (setq mu4e-get-mail-command "mbsync -a"))
```

也可以使用 --read-envelope-from 让 msmtp 自动从邮件头的 From: 字段选择正确的账号发送:

```lisp
(setq sendmail-program "/usr/bin/msmtp")
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq message-sendmail-extra-arguments '("--read-envelope-from"))
```