自建轻量邮件服务器?先了解传输原理!

AI 摘要
本文介绍了邮件传输的基本原理以及如何搭建自己的邮件服务器。邮件传输过程包括发送、传递、接收、存储和查看。 发送方使用MUA编写邮件,并通过SMTP(s)将邮件发送到MTA。 MTA会检查邮件的发件地址,并查询收件人域名的MX记录来确定邮件的投递目的地。收件方的MTA会对邮件进行验证,如DKIM、SPF和DMARC等。确认邮件后,MTA将其存储到邮件服务器中。接收方可以使用MUA从邮件服务器中下载或查看邮件。自建邮件服务器可以使用docker-mailserver快速搭建,并配置DNS记录来确保邮件的正常传输和验证。同时还提到了一些优化和检查邮件服务器的方法。
警告
本文最后更新于 2023-03-30,文中内容可能已过时。
sequenceDiagram
    participant User1;
    participant MUA1 as Mail User Agent 1;
    participant MTA1 as Mail Transfer Agent 1;
    participant DNS;
    participant MTA2 as Mail Transfer Agent 2;
    participant MDA as Mail Delivery Agent;
    participant MRA as Mail Retrieval Agent;
    participant MUA2 as Mail User Agent 2;
    participant User2;

    User1->>MUA1: 写一封邮件;
    MUA1->>+MTA1: 通过SMTP(s)发送邮件;
    MTA1->>+MTA1: 检查发信地址是否合法;
    MTA1->>+DNS: 查询收信人域名MX记录;
    DNS-->>-MTA1: 响应;
    MTA1->>+MTA2: 邮件转递;
    MTA2->>+DNS: 查询发信人域名MX/TXT记录;
    DNS-->>-MTA2: 响应;
    Note over MTA1,MTA2: 验证发信人MX/DKIM/SPF/DMARC/PTR;
    rect RGB(200,200,200);
      MTA2->>+MDA: 在收信人邮箱中存储邮件;
      MDA-->-MTA2: 确认接收邮件;
    end;
    MTA2-->>-MTA1: 确认邮件已接收;
    MTA1-->>MUA1: 邮件已发送;
    MUA1->>+MUA1: 存到已发信箱;
    MUA1-->>User1: 发送成功;
    rect RGB(200,200,200);
      MUA2->>+MRA: 访问邮箱下载邮件;
      MRA->>+MDA: 访问邮箱以检索邮件内容;
      MDA-->>-MRA: 响应邮件内容;
      MRA-->>-MUA2: 返回邮件;
      User2-)MUA2: 查看邮件;
    end;

注:

  • 灰色部分表示对方服务器
  • 仅供参考,有任何错误欢迎评论区讨论!
  • MUA(Mail User Agent)是邮件用户代理,也就是用户用来发送和接收邮件的软件。MUA通过IMAP或POP3协议与MRA通信
  • MTA(Mail Transfer Agent)是邮件传输代理,也就是邮件服务器上的软件,负责通过SMTP协议发送和转发邮件。MTA会根据收信人地址中的域名查询对应的邮件服务器,并将邮件投递给它
  • MDA(Mail Delivery Agent)是邮件投递代理,也就是负责将MTA接收到的邮件保存到本地磁盘或指定地方的软件。MDA通常会进行垃圾邮件和病毒扫描,并提供邮件过滤和自动回复等功能
  • MRA(Mail Receive Agent)是邮件接收代理,负责实现IMAP和POP3协议,与MUA进行交互。MRA可以帮助用户从邮件服务器上下载或查看邮件
  • SMTP(Simple Mail Transfer Protocol)是一种用于电子邮件传输的标准协议。它定义了如何在互联网上传输和传递电子邮件。SMTP是一个客户端/服务器协议,它使用TCP作为传输层协议,并使用25端口作为默认端口,ESMTP通过465/587端口
  • IMAP(Internet Message Access Protocol)是一种用于电子邮件存储和访问的标准协议。IMAP协议通常使用143/993端口
  • POP3(Post Office Protocol version 3)是一种用于电子邮件下载的标准协议,它允许用户通过邮件客户端从邮件服务器上下载邮件到本地计算机。它使用110端口进行传输,与SMTP协议配合使用。POP3协议的重点在于下载,不支持在服务器上管理邮件

常用的MUA有:Outlook、Apple Mail、Mutt、Fairemail

常用的MTA有:Sendmail、Postfix

常用的MDA有:procmail、dropmail、Cyrus

常用的MRA有:dovecot、Fetchmail、Getmail

邮件传输的过程可以分为以下几个步骤:

本过程将模拟 “小明” <ming@qq.com>发送至 “小红” <hong@gmail.com>

小明通过MUA编写邮件,并指定hong@gmail.com,点击发送,MUA使用SMTP(s)将邮件发送到smtp.qq.com25/465/587端口

smtp.qq.com检查发信地址是否属于该账号,如果属于,则继续投递。smtp.qq.com使用DNS查询hong@gmail.com对应域的MX记录1。查询成功后,将邮件转发给gmail-smtp-in.l.google.com

该过程最复杂,因为邮件发送并无验证身份的机制,于是后续为了防止垃圾/钓鱼/假冒诈骗邮件的传递,接收方MTA会进行各种检查来确保邮件可信,MDA也会在MTA确认收信后进行扫描病毒等处理过程。如果检查未通过,重则直接退信,轻则归入垃圾邮箱。

在接收邮件时,Gmail会检查DKIM/SPF/DMARC等记录以及PTR记录,以确保邮件的发送者是真实的、可信的,并且该邮件未被篡改。

DNSSEC
后续检查基本基于DNS系统,因此域名最好支持DNSSEC以防记录被篡改

具体来说,收信方MTA在接收邮件时,会按照以下流程进行验证:

  1. DKIM验证:检查邮件中是否包含了签名过的DKIM头部字段。如果有,Gmail会使用公钥对消息进行验证,以确保该邮件是由相应域名的私钥签名的,防止邮件被篡改
  2. SPF验证:检查邮件的发送服务器是否在发送域名的SPF记录中列出。如果不在,就会认为这封邮件可能是伪造的垃圾/钓鱼邮件
  3. DMARC验证:检查邮件是否符合发送域名的DMARC策略。DMARC可以指定如何处理没有通过DKIM或SPF验证的邮件。例如,可以要求将这些邮件标记为垃圾邮件或拒绝投递
  4. PTR验证:检查邮件所在的IP地址是否与发送邮件的域名的PTR记录相匹配。PTR记录通常用于反向DNS查询,可用于验证邮件发送方的身份

通过上述验证,收信方MTA可以判断邮件是否来自可信的发信人,并且可以有效地防止垃圾邮件、诈骗

gmail-smtp-in.l.google.com确认接收邮件后,MDA进行处理,例如扫描/自动回复等。然后将其存储在hong@gmail.com的邮箱中

如果小红用的是支持Push的MUA(例如Gmail客户端),那么现在应该已经收到了邮件提醒,打开后即可看到小明发过来的邮件

请原谅我用小明小红,咱就是个起名废

一般来讲,现在自建邮件服如果不需要图形化界面和其他附加功能,只需要最基本的发送、接受、阻止垃圾邮件,没必要去使用那些过于冗杂的邮件服务器完全解决方案,例如iRedmail

本部分使用docker-mailserver作为例子来搭建邮件服,它包含了postfix, dovecot, SpamAssian, OpenDKIM, OpenDMARC, Fail2Ban等服务,并简单配置为了开箱即用,基本上半小时就可以打造一个功能完备的邮件服务器

需要满足几个条件

  • ip和服务器域名未被列入黑名单(可通过MX Super Tool检查黑名单)
  • 开放25端口
  • 可配置rDNS/PTR记录(可选,尽量)
  • 纯净系统,安装时将hostname主机名填为邮件服务器域名,如mail.example.org

如果只是用docker-mailserver配置简单的邮件服,使用人数少,那配置无需太高,1c 512m即可

当然,要安装docker及docker-compose

如果你的邮件服务器准备于二级域名下,如mail.example.org指向它,那将hostname配置为子域名,domainname配置为apex域2即可。

此处并非填写发信域
此处仅配置邮件服务器的所在域,例如配置为mail.example.org,那后面小节中的发信域mx记录就指向它

如果直接用apex域,那就将hostname填为example.org,删掉domainname

 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
version: '3.3'
services:
  mailserver:
    image: docker.io/mailserver/docker-mailserver:latest
    container_name: mailserver
    hostname: mail
    domainname: example.org
    env_file: mailserver.env
    environment:
      - SSL_TYPE=letsencrypt
    ports:
      - "25:25"    # SMTP  (explicit TLS => STARTTLS)
      - "143:143"  # IMAP4 (explicit TLS => STARTTLS)
      - "465:465"  # ESMTP (implicit TLS)
      - "587:587"  # ESMTP (explicit TLS => STARTTLS)
      - "993:993"  # IMAP4 (implicit TLS)
    volumes:
      - ./docker-data/dms/mail-data/:/var/mail/
      - ./docker-data/dms/mail-state/:/var/mail-state/
      - ./docker-data/dms/mail-logs/:/var/log/mail/
      - ./docker-data/dms/config/:/tmp/docker-mailserver/
      - /etc/localtime:/etc/localtime:ro
      - /etc/letsencrypt:/etc/letsencrypt
    restart: always
    stop_grace_period: 1m
    cap_add:
      - NET_ADMIN
    healthcheck:
      test: "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"
      timeout: 3s
      retries: 0

以下是我自用的环境变量,使用前请修改所有example.org占位符为你自己准备配置的主发信域名

还有其它一些选项可配置,请看文档了解相关环境变量含义

强烈建议使用certbot申请证书!上一节的docker-compose.yml已经配置为兼容certbot与letsencrypt,只需在容器外用certbot申请一个邮件服务器域名的SSL证书即可使用

更多SSL配置方式请看文档

  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
OVERRIDE_HOSTNAME=
DMS_DEBUG=0
LOG_LEVEL=info
SUPERVISOR_LOGLEVEL=info
ONE_DIR=1
ACCOUNT_PROVISIONER=
POSTMASTER_ADDRESS=postmaster@example.org
ENABLE_UPDATE_CHECK=1
UPDATE_CHECK_INTERVAL=1d
PERMIT_DOCKER=none
TZ=Asia/Shanghai
NETWORK_INTERFACE=
TLS_LEVEL=
SPOOF_PROTECTION=1
ENABLE_SRS=0
ENABLE_OPENDKIM=1
ENABLE_OPENDMARC=1
ENABLE_POP3=
ENABLE_CLAMAV=0
ENABLE_RSPAMD=1
ENABLE_RSPAMD_REDIS=
ENABLE_AMAVIS=1
AMAVIS_LOGLEVEL=1
ENABLE_DNSBL=1
ENABLE_FAIL2BAN=1
FAIL2BAN_BLOCKTYPE=drop
ENABLE_MANAGESIEVE=
POSTSCREEN_ACTION=enforce
SMTP_ONLY=
SSL_TYPE=
SSL_CERT_PATH=
SSL_KEY_PATH=
SSL_ALT_CERT_PATH=
SSL_ALT_KEY_PATH=
VIRUSMAILS_DELETE_DELAY=
POSTFIX_DAGENT=
POSTFIX_MAILBOX_SIZE_LIMIT=
ENABLE_QUOTAS=1
POSTFIX_MESSAGE_SIZE_LIMIT=
CLAMAV_MESSAGE_SIZE_LIMIT=
PFLOGSUMM_TRIGGER=
PFLOGSUMM_RECIPIENT=
PFLOGSUMM_SENDER=
LOGWATCH_INTERVAL=weekly
LOGWATCH_RECIPIENT=
LOGWATCH_SENDER=
REPORT_RECIPIENT=postmaster@example.org
REPORT_SENDER=
LOGROTATE_INTERVAL=weekly
POSTFIX_INET_PROTOCOLS=all
DOVECOT_INET_PROTOCOLS=all
ENABLE_SPAMASSASSIN=1
SPAMASSASSIN_SPAM_TO_INBOX=1
ENABLE_SPAMASSASSIN_KAM=1
MOVE_SPAM_TO_JUNK=1
SA_TAG=-100000.0
SA_TAG2=5.0
SA_KILL=15.0
SA_SPAM_SUBJECT=***SPAM*****
ENABLE_FETCHMAIL=0
FETCHMAIL_POLL=300
ENABLE_LDAP=
LDAP_START_TLS=
LDAP_SERVER_HOST=
LDAP_SEARCH_BASE=
LDAP_BIND_DN=
LDAP_BIND_PW=
LDAP_QUERY_FILTER_USER=
LDAP_QUERY_FILTER_GROUP=
LDAP_QUERY_FILTER_ALIAS=
LDAP_QUERY_FILTER_DOMAIN=
DOVECOT_TLS=
DOVECOT_USER_FILTER=
DOVECOT_PASS_FILTER=
DOVECOT_MAILBOX_FORMAT=maildir
DOVECOT_AUTH_BIND=
ENABLE_POSTGREY=0
POSTGREY_DELAY=300
POSTGREY_MAX_AGE=35
POSTGREY_TEXT="Delayed by Postgrey"
POSTGREY_AUTO_WHITELIST_CLIENTS=5
ENABLE_SASLAUTHD=0
SASLAUTHD_MECHANISMS=
SASLAUTHD_MECH_OPTIONS=
SASLAUTHD_LDAP_SERVER=
SASLAUTHD_LDAP_BIND_DN=
SASLAUTHD_LDAP_PASSWORD=
SASLAUTHD_LDAP_SEARCH_BASE=
SASLAUTHD_LDAP_FILTER=
SASLAUTHD_LDAP_START_TLS=
SASLAUTHD_LDAP_TLS_CHECK_PEER=
SASLAUTHD_LDAP_TLS_CACERT_FILE=
SASLAUTHD_LDAP_TLS_CACERT_DIR=
SASLAUTHD_LDAP_PASSWORD_ATTR=
SASLAUTHD_LDAP_AUTH_METHOD=
SASLAUTHD_LDAP_MECH=
SRS_SENDER_CLASSES=envelope_sender
SRS_EXCLUDE_DOMAINS=
SRS_SECRET=
DEFAULT_RELAY_HOST=
RELAY_HOST=
RELAY_PORT=25
RELAY_USER=
RELAY_PASSWORD=

启动

docker-compose up -d

查看实时日志

docker-compose logs -f

您最好的朋友!Setup.sh!

1
2
wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/setup.sh
chmod a+x ./setup.sh

下载setup.sh后,可以直接运行以获得帮助信息

由于docker-mailserver使用虚拟域,所以任何域名的配置都会在添加相关账号后才会生成

例如使用./setup.sh email add admin@example.org "<password>"后,example.org的配置和存储位置便会生成

选择任何一个MUA:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
IMAP:
    host: mail.example.org
    port: 993
    encryption: tls
    username: admin@example.org
    password: <password>
SMTP:
    host: mail.example.org
    port: 465
    encryption: tls
    username: admin@example.org
    password: <password>

按照这样填写参数登录即可

接下来,请转到你的域名服务提供商。这里假设已经配置好邮件服务器域名为mail.example.org,且已将A记录指向服务器。发信域名为example.org

请确保域名已配置好DNSSEC

该记录用于声明为域处理邮件的服务器

类型 名称 内容 优先级 TTL
MX @ mail.example.org 10 1小时

SPF(Sender Policy Framework)用于防止邮件伪造,也就是防止垃圾邮件。可以指定哪些IP地址有权代表该域名发送电子邮件。

SPF记录语法如下:

1
v=spf1 a mx ip4:192.0.2.0/24 ip6:2001:0db8::/32 include:example.org ~all
  • a:该域名的A记录所指向的IP地址允许代表该域名发信
  • mx:该域名的MX记录所指向的IP地址允许代表该域名发信
  • ip4/ip6:允许该网段ip发信
  • include:example.com:example.com域名的SPF记录中包含的IP地址也可以代表该域名发送电子邮件
  • ~all:表示除了上述机制之外的所有IP地址都可以尝试代表该域名发送电子邮件,但是会被标记为垃圾邮件。如果将此处改为-all,那除了前面被允许的地址外,任何地址都不可以代表该域名发信。建议使用-all

建议配置为:

类型 名称 内容 TTL
TXT @ v=spf1 mx -all 1小时

DMARC(Domain-based Message Authentication, Reporting & Conformance)基于SPF和DKIM,可以让邮件接收方验证发件人的身份,并向域名所有者提供有关发件人身份验证结果的报告

当邮件接收方收到一封电子邮件时,它会通过SPF和DKIM技术验证发件人的身份。如果发件人的电子邮件地址与SPF记录和DKIM签名匹配,则认为该邮件是合法的;否则就认为该邮件可能是垃圾邮件或欺诈邮件。如果域名所有者启用了DMARC,邮件接收方还会将验证结果报告给域名所有者,以便其进一步分析和处理

DMARC 记录语法如下:

1
v=DMARC1; p=quarantine; sp=none; rua=mailto:postmaster@example.org; ruf=mailto:postmaster@example.org;
  • p: 用于指定如何处理邮件,有三种取值:none 表示只报告,不采取任何动作,quarantine 表示将邮件放入收件人的垃圾邮件文件夹中,reject 表示直接拒绝邮件。
  • sp: 用于指定子域名的处理方式,取值同 p
  • rua: 指定接收邮件验证报告的地址,可以是多个邮箱地址,用逗号分隔。
  • ruf: 指定接收邮件处理报告地址,其它同上

建议配置为:

类型 名称 内容 TTL
TXT _dmarc v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:postmaster@example.org; ruf=mailto:postmaster@example.org; 1小时

首先,通过./setup.sh config dkim domain "<domain>"生成发信域名的DKIM公/私钥

接着,查看docker-data/dms/config/opendkim/keys/<domain>/<selector>.txt即可找到需要发布到DNS的DKIM公钥,形如<selector>._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=xxxxxxx" "xxxxxxx" "xxxxxxx" ) ; ----- DKIM key mail for example.org

使用时需去掉引号与空格,将引号内内容发布至dns,示例如下:

类型 名称 内容 TTL
TXT <selector>._domainkey v=DKIM1; h=sha256; k=rsa; p=xxxxxxxxxxxxxxxxxxxxx 1小时

由于使用虚拟域,docker-mailserver不存在主域名的说法,任何添加了账号的域名都可被使用来发信,因此请谨慎添加账号,以防发送假冒邮件被退信导致域名/ip进黑名单

按照上文,已经配置好example.org,该如何添加第二个test.com

只需要按照从"使用setup.sh添加账号"这个步骤起,将发信域名全换为准备添加的第二个域名,重新走一边流程,就可以正常发信/收信了

多个域名同理

PTR 记录提供 IP 地址和域名之间的映射关系

在邮件传输过程中,邮件服务器会根据 IP 地址来确定邮件发送方的域名。如果邮件服务器收到了一封来自未知 IP 地址的邮件,它会尝试使用 PTR 记录查找该 IP 地址对应的域名。如果查找成功,并且该域名与邮件中的发件人域名匹配,那么该邮件就被认为是合法的

例如,iCloud Mail某一台mx服务器的ip地址为17.42.251.62,那么dig -x 17.42.251.62即可反查到该ip指向的域名

DNSWL是一个不错的ip地址库,可以将域名与其匹配的ip认证为白名单

SpamHaus也可用于检查是否为黑名单,并提供自助申诉

Mail Tester可以非常方便地看出得分,10分基本上就不可能被放进垃圾邮箱里了

得瑟一下


  1. 该记录对于大型邮件服务提供商而言,一般会有多个MTA,这只是优先级最高的那个。例如通过dig MX gmail.com可以看到gmail具有五个MX记录alt4.gmail-smtp-in.l.google.com,gmail-smtp-in.l.google.com,alt3.gmail-smtp-in.l.google.com,alt2.gmail-smtp-in.l.google.com,alt1.gmail-smtp-in.l.google.com ↩︎

  2. Apex域是指域名中的最高层级,不包括任何子域名。例如,在www.example.org域中,example.org是apex域 ↩︎