使用 Haraka 搭建 SMTP 邮件服务

Prologue

用自己的域名当作邮箱有一段时间了,用了网易企业邮箱作为解决方案
使用门槛还是很低的,[准备一个域名]、[验证域名的MX记录]就行了,适合懒人
当然免费版有发送间隔限制,要商用(发很多邮件的情况下)还是不推荐

当然收发并不需要同一个服务器,所以决定把发件服务(SMTP)分离出去,单独自建
至于可选择的开源方案有很多,这次是随便选了个,做一次尝试和记录
文章也是简答的记录了下 Haraka 配置过程,我个人的话、也并不推荐你使用它

Haraka 是一个使用 Nodejs 写的高性能 SMTP Server

官网文档:https://haraka.github.io/

haraka/Haraka

A fast, highly extensible, and event driven SMTP server

Preparations

首先先搞懂几个名词SMTPMXSPF

SMTP

当前 Email 通信,还是在使用 SMTP 这个协议。SMTP (Simple Mail Transfer Protocol) 即「简单邮件传输协议」
SMTP还是比较简陋的,根据 SMTP 的规则,发件人的邮箱地址是根据发件人任意声明的

SPF

所以这边还需要了解一个概念SPF(Sender Policy Framework) 即「发件人策略框架」
SPF其实是一条DNS记录

打个比方,邮件服务器收到了一封邮件,发件人主机IP是111.11.1.0,声称的发件人为email@example.com
想确认发件人是不是伪造的,邮件服务器会去找到example.com域名的SPF记录
如果这个域名的SPF记录设置了111.11.1.0为白名单,那这封Mail就是可信的
如果不在白名单,那可能会被标记可疑/垃圾Mail

因为不怀好心的人虽然可以「声称」他的邮件来自example.com
但是他却无权操作example.com的 DNS 记录;同时他也无法伪造自己的 IP 地址
因此 SPF 是很有效的
当前基本上所有的邮件服务提供商(例如 Gmail、QQ 邮箱等)都会验证它。

MX Record

邮件交换记录,本质上也是域名的一条DNS记录。决定了发件人投递的服务器

当你要往ame@abyss.moe投递邮件的时候、发件服务器会先查找abyss.moeMX Record

比如我的设置如上图所示,中间的数字是Priority,越小越高
所以发件服务器会把Mail先投给mx.ym.163.com
但如果没成功,会继续投递给下一条MX解析记录的IP

Create an SPF TXT record

上面是邮件的基本概念,了解即可
这次我准备搭建的是只是发件服务,所以只需设置SPF记录
要设置SPF的话,在域名供应商的DNS解析页面,添加TXT记录即可

请注意,DNS Type 不要选择 SPF,请选择TXT records。因为 SPF 记录 已经被 TXT 记录 代替了
DNS specifications have deprecated the SPF record type in favor of TXT records.

上图意义为:允许当前域名的 mx 记录对应的 IP 地址
具体语法你可以参考网上的文档

1
2
3
4
5
6
7
### 这边再举几个例子

"v=spf1 ip4:192.168.0.1/16 -all"
只允许在 192.168.0.1 ~ 192.168.255.255 范围内的 IP

"v=spf1 mx mx:deferrals.example.com -all"
允许当前域名和 deferrals.example.com 的 mx 记录对应的 IP 地址。

添加好之后,我们可以在服务器上安装Haraka

Installing Haraka

HarakaJS编写,首先要安装Nodejs

安装 Nodejs 可以参考
https://github.com/nodesource/distributions/blob/master/README.md

Debian and Ubuntu based distributions

1
2
curl -fsSL https://deb.nodesource.com/setup_15.x | bash -
apt-get install -y nodejs

Enterprise Linux based distributions

1
yum install -y nodejs

npmNodejs的包管理器,npm也会自动一起安装,接着使用npm安装Haraka

1
npm install -g Haraka

如果没有出错的话,执行haraka看到下面的返回,就算成功了

Configure Haraka

创建配置文件

1
haraka -i /etc/haraka

会自动生成需要的配置文件和目录

1
2
3
4
5
6
7
8
9
create: /etc/haraka
create: /etc/haraka/plugins
create: /etc/haraka/docs
create: /etc/haraka/config
create: /etc/haraka/config/smtp.ini
create: /etc/haraka/config/log.ini
create: /etc/haraka/config/plugins
create: /etc/haraka/config/dkim
create: /etc/haraka/config/dkim/dkim_key_gen.sh

启动验证插件

先编辑/etc/haraka/config/plugins,把auth/flat_file这一行的注释#去掉,启用这个插件

flat_file 插件的说明
https://haraka.github.io/plugins/auth/flat_file/

config目录下新建auth_flat_file.ini,填写一下内容
user1=password1是用户名和密码,可以自己更换

/etc/haraka/config/auth_flat_file.ini
1
2
3
4
5
[core]
methods=PLAIN,LOGIN,CRAM-MD5

[users]
user1=password1

本地测试

先在终端运行 Haraka

1
haraka -c /etc/haraka

启动另一个终端
接着我们使用swaks测试一下,swaks是一个SMTP测试工具
下载swaks编译好的文件执行即可使用

1
2
3
4
5
## 下载 swaks 二进制包
## http://www.jetmore.org/john/code/swaks/installation.html

curl -O https://jetmore.org/john/code/swaks/files/swaks-20201014.0/swaks
chmod 755 ./swaks

来测试一下,下面是示例用法
-au-ap后面参数是刚刚配置的用户名密码
-t后面是收件人,-f后面是发件人

1
2
3
4
5
6
7
./swaks -f ame@abyss.moe \
-t to@qq.com \
-s localhost \
--header "Subject:标题" \
--body "内容" \
-au user1 \
-ap password1

如果返回250 Message Queued,即为成功,可以检查下邮箱有没有收到
被拒收了的话,可能是你SPF配置问题

配置tls证书

本地通过Haraka发送邮件是不需要配置tls
如果你用另一台服务器,则强制需要通过tls加密通信

Outbound Mail with Haraka
https://haraka.github.io/tutorials/SettingUpOutbound/
https://haraka.github.io/core/Outbound/

首先申请域名的TLS证书
这里给个实例,具体过程可以参考其他的一些文章
由于我使用了Cloudflare,这边直接Cloudflare APIacme.sh签发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
### 设置 Key
export CF_Key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Email="xxxxxx@xxx.xxx"

### 获取 acme.sh
git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install


### 通过API添加txt记录,然后会自动验证签发
./acme.sh --issue --dns dns_cf -d "abyss.moe" -d "*.abyss.moe"

### 成功的话,结果如下
### fullchain 是证书链,包含了 cert、intermediate CA cert

## Your cert is in /root/.acme.sh/abyss.moe/abyss.moe.cer
## Your cert key is in /root/.acme.sh/abyss.moe/abyss.moe.key
## The intermediate CA cert is in /root/.acme.sh/abyss.moe/ca.cer
## And the full chain certs is there: /root/.acme.sh/abyss.moe/fullchain.cer

启用 TLS

/etc/haraka/config/plugins里把tls前面的#去掉,启用tls插件
编辑config/tls.ini

Haraka TLS plugin
https://haraka.github.io/plugins/tls/

/etc/haraka/config/tls.ini
1
2
key=/usr/local/nginx/conf/ssl/abyss.moe/abyss.moe.key
cert=/usr/local/nginx/conf/ssl/abyss.moe/fullchain.cer

测试加密通信

然后重启下 Haraka
在另一台服务器使用swaks试一下
把刚刚的参数加上-tls, -s后面参数换成你的域名

1
2
3
4
5
6
7
8
./swaks -f ame@abyss.moe \
-t to@163.com \
-s abyss.moe \
--header "Subject:标题" \
--body "内容" \
-au user1 \
-ap password1 \
-tls

如果出现*** TLS not available: requires Net::SSLeay. Exiting,请尝试安装下面的包

1
2
sudo apt-get install libnet-ssleay-perl
sudo apt-get install libcrypt-ssleay-perl

成功的话,还是会返回250 Message Queued,检查下邮箱应该会收到

Some optimizes

dnsbl插件默认是启用的,该插件用于检查发件人的 IP 地址是否在反垃圾邮件数据库(DNSBL)中
可以在/etc/haraka/config/plugins禁用它

设置smtp.ini.nodes的参数,这个配置项用于控制 Haraka 启动时创建的 SMTP 进程数量
默认值使用的是1,说明可以在Github的文档内找到:Performance-Tuning

把配置文件/etc/haraka/config/smtp.ini里的nodes=cpus取消注释

另外,当使用nodes=cpus运行时,将NodeJs的环境变量NODE_CLUSTER_SCHED_POLICY
设置为none,可以显著提高新连接套接字发送SMTP banner之前的等待时间

如果没有这个设置,有时可能需要超过30秒才会发送SMTP banner
这会导致许多客户端在此之前断开连接

使用systemd启动Haraka的时候,在 ExecStart 行之前添加一个新的行
设置Environment指令来定义环境变量

1
Environment=NODE_CLUSTER_SCHED_POLICY=none

Use 587 port

发送电子邮件,建议优先选择使用端口 587,并启用 STARTTLS 加密。

SMTP(Simple Mail Transfer Protocol)用于电子邮件的传输。
[25 , 465 , 587] 是 SMTP 常用端口
 
25 端口
是默认端口,通常使用非加密通信,所以不建议使用
 
465 端口
用于通过 SSL/TLS 加密连接发送邮件。但是,使用这个端口的方式已经被弃用,不推荐使用
 
587 端口
SMTP STARTTLS的端口。STARTTLS 是一种在普通的 SMTP 连接上启用加密的方法。请优先选择使用端口 587,并启用 STARTTLS 加密

修改配置文件 /etc/haraka/config/smtp.ini 将第3行监听的端口,改成

1
listen=[::0]:25,587

再次使用swaks测试一下,加上--port 587 参数。可以收到邮件就完美了

Create a daemon process

可以使用Systemd在后台运行
新建一个文件:/etc/systemd/system/haraka.service

/etc/systemd/system/haraka.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description= Haraka Mail Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/haraka -c /etc/haraka
Environment=NODE_CLUSTER_SCHED_POLICY=none
User=root
Group=root
Restart=always

[Install]
WantedBy=multi-user.target

启用该服务

1
systemctl enable haraka --now

参考过的一些文章
SPF 记录:原理、语法及配置方法简介 - Renfei Song’s Blog
haraka收发邮件初级教程 - SHANG Blog