跳到主要内容

邮件协议详解:SMTP、POP3 与 IMAP

电子邮件是互联网最早、最广泛使用的应用之一。尽管即时通讯工具层出不穷,电子邮件仍然是企业通信、身份验证、通知推送的重要基础设施。理解邮件协议的工作原理,对于运维邮件服务器、开发邮件应用、排查邮件问题都至关重要。

电子邮件系统概述

邮件系统的组成

一封邮件从发送到接收,需要多个组件协同工作:

发件人                         收件人
| |
v v
┌──────────┐ SMTP ┌──────────────────┐ SMTP ┌──────────┐
│ MUA │ ─────────> │ MTA (发送服务器) │ ─────────> │ MTA │
│ (客户端) │ │ │ │(接收服务器)│
└──────────┘ └──────────────────┘ └──────────┘

v
┌──────────┐
│ MDA │
│ (投递代理)│
└──────────┘

v
┌──────────┐
│ 邮箱存储 │
└──────────┘

POP3/IMAP │
┌──────┘
v
┌──────────┐
│ MUA │
│ (客户端) │
└──────────┘

MUA(Mail User Agent,邮件用户代理):用户用于收发邮件的客户端软件,如 Outlook、Foxmail、Apple Mail 等。

MTA(Mail Transfer Agent,邮件传输代理):负责邮件的传输和路由,如 Postfix、Exim、Sendmail、Microsoft Exchange。

MDA(Mail Delivery Agent,邮件投递代理):将收到的邮件投递到用户的邮箱存储中,如 Dovecot、Procmail。

邮件传输流程

  1. 发件人撰写邮件:使用 MUA(邮件客户端)撰写邮件
  2. 提交到发送服务器:MUA 通过 SMTP 协议将邮件发送到发件人的邮件服务器
  3. 路由查找:发送服务器查询收件人域名对应的 MX 记录,找到接收服务器
  4. 传输邮件:发送服务器通过 SMTP 协议将邮件传输到接收服务器
  5. 本地投递:接收服务器通过 MDA 将邮件存储到收件人的邮箱
  6. 接收邮件:收件人通过 POP3 或 IMAP 协议从服务器获取邮件

SMTP 协议

SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是用于发送邮件的核心协议,由 RFC 5321 定义。它负责将邮件从发件人传送到收件人的邮件服务器。

SMTP 基本特点

  • 基于 TCP:使用端口 25(默认)、587(提交端口)或 465(SMTPS)
  • 文本协议:命令和响应都是可读的 ASCII 文本
  • 推协议:邮件由发送方主动推送给接收方
  • 存储转发:邮件可以经过多个 MTA 中继转发

SMTP 会话流程

一个完整的 SMTP 会话包含三个阶段:

SMTP 命令详解

命令语法说明
EHLOEHLO <domain>扩展的 Hello,启动会话并声明客户端身份
HELOHELO <domain>基本的 Hello(旧版,不推荐)
MAIL FROMMAIL FROM:<address>指定发件人地址
RCPT TORCPT TO:<address>指定收件人地址(可多次使用)
DATADATA开始传输邮件内容,以单独一行的 . 结束
RSETRSET重置当前会话,取消当前邮件
VRFYVRFY <address>验证邮箱地址是否存在(通常被禁用)
NOOPNOOP空操作,用于保持连接
QUITQUIT关闭连接

EHLO vs HELO:EHLO 是扩展版本,服务器会返回支持的功能列表(如 STARTTLS、AUTH、PIPELINING 等)。现代邮件系统都应该使用 EHLO。

SMTP 响应码

SMTP 响应码由三位数字组成,第一位表示类别:

类别含义示例
2xx成功250 请求的操作完成
3xx需要更多信息354 开始邮件输入
4xx暂时失败450 邮箱暂时不可用
5xx永久失败550 邮箱不存在

常见响应码:

响应码含义
220服务就绪
221服务关闭传输通道
250请求的操作完成
354开始邮件输入,以 . 结束
421服务不可用,关闭传输通道
450邮箱不可用(忙)
451本地错误,操作中止
452存储空间不足
500命令无法识别
501参数语法错误
502命令未实现
503命令顺序错误
550邮箱不可用
551用户不在本地
552存储空间超出
553邮箱名无效
554事务失败

SMTP 认证

SMTP 最初设计时没有认证机制,任何人都可以发送邮件。这导致了垃圾邮件泛滥。现在,邮件提交端口(587)通常要求认证:

AUTH PLAIN:Base64 编码的用户名和密码

C: AUTH PLAIN
S: 334
C: AHVzZXJuYW1lAHBhc3N3b3Jk
S: 235 2.7.0 Authentication successful

AUTH LOGIN:分别发送用户名和密码

C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: dXNlcm5hbWU=
S: 334 UGFzc3dvcmQ6
C: cGFzc3dvcmQ=
S: 235 2.7.0 Authentication successful

SMTP 与 TLS

STARTTLS:将明文连接升级为加密连接

C: EHLO client.example.com
S: 250-STARTTLS
C: STARTTLS
S: 220 2.0.0 Ready to start TLS
[TLS 握手]
C: EHLO client.example.com
S: 250-AUTH PLAIN LOGIN

SMTPS(SMTP over SSL):从连接开始就使用 TLS,端口 465

使用 Telnet 测试 SMTP

# 连接邮件服务器
telnet mail.example.com 25

# 会话示例
220 mail.example.com ESMTP Postfix
EHLO client.example.com
250-mail.example.com
250-PIPELINING
250-SIZE 10240000
250-STARTTLS
250 AUTH PLAIN LOGIN
MAIL FROM:<[email protected]>
250 2.1.0 Ok
RCPT TO:<[email protected]>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
From: [email protected]
To: [email protected]
Subject: Test

This is a test email.
.
250 2.0.0 Ok: queued as ABC123
QUIT
221 2.0.0 Bye

Python 发送邮件示例

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr

def send_email(smtp_server, smtp_port, username, password,
from_addr, to_addr, subject, content, html_content=None):
"""
发送邮件

Args:
smtp_server: SMTP 服务器地址
smtp_port: SMTP 端口(587 或 465)
username: 认证用户名
password: 认证密码
from_addr: 发件人地址
to_addr: 收件人地址(可以是列表)
subject: 邮件主题
content: 纯文本内容
html_content: HTML 内容(可选)
"""
# 创建邮件对象
msg = MIMEMultipart('alternative')
msg['From'] = formataddr(('发件人名称', from_addr))
msg['To'] = ', '.join(to_addr) if isinstance(to_addr, list) else to_addr
msg['Subject'] = subject

# 添加纯文本内容
msg.attach(MIMEText(content, 'plain', 'utf-8'))

# 添加 HTML 内容(如果提供)
if html_content:
msg.attach(MIMEText(html_content, 'html', 'utf-8'))

# 连接服务器并发送
try:
# 使用 STARTTLS(端口 587)
if smtp_port == 587:
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(username, password)
server.sendmail(from_addr, to_addr, msg.as_string())

# 使用 SMTPS(端口 465)
elif smtp_port == 465:
with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
server.login(username, password)
server.sendmail(from_addr, to_addr, msg.as_string())

print("邮件发送成功")
return True

except smtplib.SMTPException as e:
print(f"邮件发送失败: {e}")
return False

# 使用示例
send_email(
smtp_server='smtp.example.com',
smtp_port=587,
username='[email protected]',
password='password',
from_addr='[email protected]',
to_addr='[email protected]',
subject='测试邮件',
content='这是一封测试邮件。',
html_content='<html><body><h1>这是一封测试邮件</h1></body></html>'
)

POP3 协议

POP3(Post Office Protocol version 3,邮局协议第 3 版)是用于从邮件服务器获取邮件的协议,由 RFC 1939 定义。它的设计理念是"下载并删除",适合单设备使用场景。

POP3 特点

  • 端口:110(明文),995(POP3S)
  • 简单:命令简单,功能有限
  • 离线模式:邮件下载后可以离线阅读
  • 单向:只能获取邮件,不能修改服务器状态(除了删除)
  • 适合单设备:不适合多设备同步

POP3 会话流程

POP3 命令

命令语法说明
USERUSER <username>发送用户名
PASSPASS <password>发送密码
STATSTAT获取邮箱状态(邮件数和总大小)
LISTLIST [msg]列出邮件(或指定邮件的大小)
RETRRETR <msg>获取指定邮件的内容
DELEDELE <msg>标记删除指定邮件
NOOPNOOP空操作
RSETRSET重置所有删除标记
QUITQUIT提交更改并断开连接
TOPTOP <msg> <n>获取邮件头部和前 n 行
UIDLUIDL [msg]获取邮件的唯一标识符

POP3 响应

  • +OK:操作成功
  • -ERR:操作失败

POP3 的限制

  1. 不支持文件夹:无法在服务器上管理文件夹
  2. 不支持标记:无法标记邮件为已读、星标等
  3. 同步困难:删除后其他设备无法获取该邮件
  4. 只下载完整邮件:无法只下载邮件头部

Python POP3 示例

import poplib
from email.parser import Parser

def fetch_emails(pop_server, port, username, password, delete=False):
"""
从 POP3 服务器获取邮件

Args:
pop_server: POP3 服务器地址
port: 端口(110 或 995)
username: 用户名
password: 密码
delete: 是否在获取后删除服务器上的邮件
"""
emails = []

try:
# 连接服务器
if port == 995:
server = poplib.POP3_SSL(pop_server, port)
else:
server = poplib.POP3(pop_server, port)

# 认证
server.user(username)
server.pass_(password)

# 获取邮箱状态
status = server.stat()
print(f"邮箱状态: {status[0]} 封邮件, {status[1]} 字节")

# 获取邮件列表
msg_list = server.list()

# 遍历邮件
for i in range(len(msg_list[1])):
msg_num = i + 1
# 获取邮件内容
msg_content = b'\r\n'.join(server.retr(msg_num)[1])
email_message = Parser().parsestr(msg_content.decode('utf-8', errors='ignore'))
emails.append(email_message)

# 标记删除
if delete:
server.dele(msg_num)

# 关闭连接
server.quit()
return emails

except Exception as e:
print(f"获取邮件失败: {e}")
return None

# 使用示例
emails = fetch_emails('pop.example.com', 995, '[email protected]', 'password')
for email in emails:
print(f"发件人: {email['From']}")
print(f"主题: {email['Subject']}")
print("---")

IMAP 协议

IMAP(Internet Message Access Protocol,互联网邮件访问协议)是功能更强大的邮件获取协议,由 RFC 3501 定义。它的设计理念是"服务器端管理",适合多设备同步使用。

IMAP 特点

  • 端口:143(明文),993(IMAPS)
  • 服务器端存储:邮件保存在服务器上
  • 双向同步:客户端状态与服务器同步
  • 文件夹支持:支持创建、管理文件夹
  • 搜索功能:支持服务器端搜索
  • 部分获取:可以只获取邮件的一部分
  • 多设备同步:适合在多个设备上使用

IMAP 会话流程

IMAP 命令

IMAP 命令以标签(tag)开头,用于匹配请求和响应。

命令语法说明
CAPABILITYtag CAPABILITY查询服务器支持的功能
LOGINtag LOGIN user pass登录认证
AUTHENTICATEtag AUTHENTICATE mechanismSASL 认证
SELECTtag SELECT mailbox选择邮箱
EXAMINEtag EXAMINE mailbox以只读模式打开邮箱
CREATEtag CREATE mailbox创建邮箱
DELETEtag DELETE mailbox删除邮箱
RENAMEtag RENAME old new重命名邮箱
SUBSCRIBEtag SUBSCRIBE mailbox订阅邮箱
UNSUBSCRIBEtag UNSUBSCRIBE mailbox取消订阅
LISTtag LIST ref pattern列出邮箱
STATUStag STATUS mailbox (items)获取邮箱状态
FETCHtag FETCH seq items获取邮件
STOREtag STORE seq item value存储邮件标志
SEARCHtag SEARCH criteria搜索邮件
COPYtag COPY seq mailbox复制邮件
MOVEtag MOVE seq mailbox移动邮件
EXPUNGEtag EXPUNGE永久删除标记为删除的邮件
CLOSEtag CLOSE关闭邮箱(隐式 EXPUNGE)
LOGOUTtag LOGOUT注销连接

邮件标志

标志说明
\Seen已读
\Answered已回复
\Flagged已标记/星标
\Deleted已删除(等待 EXPUNGE)
\Draft草稿
\Recent最近到达(本次会话新到)

IMAP 数据项

FETCH 命令可以获取邮件的不同部分:

数据项说明
ALL宏:等同于 FLAGS INTERNALDATE RFC822.SIZE ENVELOPE
FAST宏:等同于 FLAGS INTERNALDATE RFC822.SIZE
FULL宏:等同于 FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY
BODY[]完整邮件内容
BODY[HEADER]只获取邮件头
BODY[TEXT]只获取邮件正文
BODY[1]获取第一个 MIME 部分
RFC822完整邮件(等同于 BODY[])
RFC822.HEADER只获取邮件头
RFC822.TEXT只获取邮件正文
ENVELOPE邮件信封(发件人、收件人等)
FLAGS邮件标志

IMAP 搜索条件

# 搜索未读邮件
SEARCH UNSEEN

# 搜索来自特定发件人的邮件
SEARCH FROM "[email protected]"

# 搜索主题包含关键词的邮件
SEARCH SUBJECT "important"

# 搜索特定日期之后的邮件
SEARCH SINCE 01-Jan-2024

# 组合条件
SEARCH UNSEEN FROM "[email protected]" SINCE 01-Jan-2024

Python IMAP 示例

import imaplib
import email
from email.header import decode_header

class IMAPClient:
"""IMAP 客户端封装"""

def __init__(self, server, port=993, use_ssl=True):
self.server = server
self.port = port
self.use_ssl = use_ssl
self.conn = None

def connect(self, username, password):
"""连接并登录"""
try:
if self.use_ssl:
self.conn = imaplib.IMAP4_SSL(self.server, self.port)
else:
self.conn = imaplib.IMAP4(self.server, self.port)

self.conn.login(username, password)
print("IMAP 连接成功")
return True
except Exception as e:
print(f"IMAP 连接失败: {e}")
return False

def list_folders(self):
"""列出所有邮箱文件夹"""
status, folders = self.conn.list()
result = []
for folder in folders:
# 解析文件夹名称
parts = folder.decode().split('"')
if len(parts) >= 3:
result.append(parts[-2])
return result

def select_folder(self, folder='INBOX'):
"""选择邮箱文件夹"""
status, data = self.conn.select(folder)
if status == 'OK':
print(f"已选择: {folder}")
return int(data[0]) # 返回邮件数量
return 0

def search(self, criteria='ALL'):
"""搜索邮件"""
status, message_ids = self.conn.search(None, criteria)
if status == 'OK':
return message_ids[0].split()
return []

def fetch_email(self, msg_id):
"""获取单封邮件"""
status, data = self.conn.fetch(msg_id, '(RFC822)')
if status == 'OK':
raw_email = data[0][1]
return email.message_from_bytes(raw_email)
return None

def fetch_headers(self, msg_id):
"""只获取邮件头"""
status, data = self.conn.fetch(msg_id, '(BODY[HEADER])')
if status == 'OK':
return email.message_from_bytes(data[0][1])
return None

def set_flags(self, msg_id, flags):
"""设置邮件标志"""
status, data = self.conn.store(msg_id, '+FLAGS', flags)
return status == 'OK'

def mark_as_read(self, msg_id):
"""标记为已读"""
return self.set_flags(msg_id, '\\Seen')

def mark_as_unread(self, msg_id):
"""标记为未读"""
status, data = self.conn.store(msg_id, '-FLAGS', '\\Seen')
return status == 'OK'

def delete_email(self, msg_id):
"""标记删除邮件"""
status, data = self.conn.store(msg_id, '+FLAGS', '\\Deleted')
return status == 'OK'

def expunge(self):
"""永久删除已标记的邮件"""
self.conn.expunge()

def move_email(self, msg_id, dest_folder):
"""移动邮件到另一个文件夹"""
status, data = self.conn.copy(msg_id, dest_folder)
if status == 'OK':
self.delete_email(msg_id)
return True
return False

def close(self):
"""关闭连接"""
if self.conn:
try:
self.conn.close()
self.conn.logout()
except:
pass


def decode_str(header_value):
"""解码邮件头中的编码字符串"""
if header_value is None:
return ""
decoded_parts = decode_header(header_value)
result = []
for part, charset in decoded_parts:
if isinstance(part, bytes):
result.append(part.decode(charset or 'utf-8', errors='ignore'))
else:
result.append(part)
return ''.join(result)


# 使用示例
if __name__ == '__main__':
client = IMAPClient('imap.example.com', 993)

if client.connect('[email protected]', 'password'):
# 列出文件夹
folders = client.list_folders()
print(f"文件夹: {folders}")

# 选择收件箱
count = client.select_folder('INBOX')
print(f"收件箱邮件数: {count}")

# 搜索未读邮件
unread_ids = client.search('UNSEEN')
print(f"未读邮件: {len(unread_ids)} 封")

# 获取最新邮件
if count > 0:
all_ids = client.search('ALL')
latest_id = all_ids[-1]
email_msg = client.fetch_email(latest_id)

print(f"发件人: {decode_str(email_msg['From'])}")
print(f"主题: {decode_str(email_msg['Subject'])}")
print(f"日期: {email_msg['Date']}")

client.close()

POP3 vs IMAP 对比

特性POP3IMAP
邮件存储本地服务器
多设备同步不支持支持
文件夹管理不支持支持
已读/未读状态不同步同步
部分下载不支持支持
服务器搜索不支持支持
离线阅读支持(下载后)需要预先下载
服务器空间节省占用
复杂度简单复杂
适用场景单设备、存档多设备、日常使用

建议:现代邮件系统通常推荐使用 IMAP,方便多设备同步。如果需要本地存档大量邮件,可以结合 POP3 使用。

邮件安全认证

邮件系统面临的主要安全威胁包括:垃圾邮件、钓鱼邮件、邮件伪造。SPF、DKIM、DMARC 三种技术共同构成了邮件安全认证的基础。

SPF(Sender Policy Framework)

SPF 让域名所有者声明哪些服务器被授权发送来自该域名的邮件。

工作原理

SPF 记录语法

v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.100 include:_spf.google.com -all
机制说明
ip4:cidr授权的 IPv4 地址范围
ip6:cidr授权的 IPv6 地址范围
a:domain授权域名 A 记录指向的 IP
mx:domain授权域名 MX 记录指向的服务器
include:domain包含另一个域名的 SPF 记录
exists:domain如果域名存在则通过
all所有其他情况的处理

限定符

限定符说明
+通过(默认)
-失败(拒绝)
~软失败(标记但接受)
?中性(无策略)

配置示例

# 只允许特定 IP 发送
v=spf1 ip4:192.0.2.100 -all

# 允许多个 IP 范围
v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 -all

# 包含第三方邮件服务
v=spf1 include:_spf.google.com include:sendgrid.net -all

# 测试模式(不实际拒绝)
v=spf1 ip4:192.0.2.0/24 ~all

DKIM(DomainKeys Identified Mail)

DKIM 为邮件添加数字签名,验证邮件确实来自声明的域名且未被篡改。

工作原理

DKIM-Signature 头部

DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=default;
h=From:To:Subject:Date;
bh=2jUSO9PuvldFwDnhKvRVdQ==;
b=encrypted-signature-data;
字段说明
vDKIM 版本
a签名算法(rsa-sha256 或 rsa-sha1)
d签名域名
s选择器(用于查找公钥)
h被签名的头部字段列表
bh邮件正文的哈希值
b数字签名

DNS 记录格式

default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."

生成 DKIM 密钥

# 使用 opendkim 工具生成
opendkim-genkey -t -s default -d example.com

# 会生成两个文件:
# default.private - 私钥(保存在邮件服务器)
# default.txt - DNS TXT 记录内容(发布到 DNS)

DMARC(Domain-based Message Authentication, Reporting, and Conformance)

DMARC 告诉邮件接收方如何处理 SPF 和 DKIM 验证失败的邮件,并提供报告机制。

工作原理

1. 接收邮件
2. 检查 SPF
3. 检查 DKIM
4. 检查 DMARC 策略
5. 根据策略决定:通过、隔离、拒绝
6. 发送报告给域名所有者

DMARC 记录语法

_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]; pct=100; adkim=s; aspf=s"
标签说明示例值
vDMARC 版本DMARC1
p策略none, quarantine, reject
pct应用策略的百分比0-100
rua聚合报告发送地址mailto:[email protected]
ruf取证报告发送地址mailto:[email protected]
adkimDKIM 对齐模式s(严格), r(宽松)
aspfSPF 对齐模式s(严格), r(宽松)

策略级别

策略说明用途
p=none只监控收集报告,了解现状
p=quarantine隔离将失败邮件放入垃圾邮件文件夹
p=reject拒绝直接拒绝失败的邮件

部署步骤

  1. 配置 SPF:确保合法的发送服务器都在 SPF 记录中
  2. 配置 DKIM:为邮件服务器配置 DKIM 签名
  3. 监控模式:设置 p=none,收集报告,了解验证情况
  4. 调整:根据报告调整 SPF 和 DKIM 配置
  5. 隔离模式:设置 p=quarantine,观察效果
  6. 拒绝模式:设置 p=reject,完全启用 DMARC

DMARC 报告示例

<feedback>
<report_metadata>
<org_name>google.com</org_name>
<email>[email protected]</email>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim>
<aspf>r</aspf>
<p>reject</p>
<pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>192.0.2.100</source_ip>
<count>10</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
</record>
</feedback>

三者协同工作

                    邮件到达


┌─────────────────┐
│ SPF 检查 │
│ 发送服务器是否 │
│ 被授权? │
└────────┬────────┘

┌──────────┴──────────┐
│ │
▼ ▼
[通过] [失败]
│ │
│ ▼
│ 记录结果
│ │
▼ │
┌─────────────────┐ │
│ DKIM 检查 │ │
│ 签名是否有效? │ │
└────────┬────────┘ │
│ │
┌────────┴────────┐ │
│ │ │
▼ ▼ │
[通过] [失败] │
│ │ │
│ ▼ │
│ 记录结果 │
│ │ │
└────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ DMARC 检查 │◄──────────┘
│ 根据策略决定 │
│ 如何处理 │
└────────┬────────┘

┌────────┴────────┐
│ │
▼ ▼
[通过] [隔离/拒绝]
│ │
▼ ▼
正常投递 垃圾邮件/退回

邮件服务器配置实践

Postfix 基本配置

Postfix 是最流行的开源 MTA 之一。

主配置文件 /etc/postfix/main.cf

# 基本设置
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4

# 邮件存储
mail_location = maildir:/var/mail/vhosts/%d/%n

# 认证设置
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth

# TLS 设置
smtpd_tls_cert_file = /etc/ssl/certs/mail.example.com.crt
smtpd_tls_key_file = /etc/ssl/private/mail.example.com.key
smtpd_tls_security_level = may
smtp_tls_security_level = may

# 限制设置
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain

# SPF 检查(需要安装 postfix-policyd-spf-python)
policyd-spf_time_limit = 3600s
smtpd_recipient_restrictions =
...,
check_policy_service unix:private/policyd-spf

# DKIM 签名(需要安装 opendkim)
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

Dovecot 配置

Dovecot 是流行的 IMAP/POP3 服务器。

主配置文件 /etc/dovecot/dovecot.conf

# 启用的协议
protocols = imap pop3

# 监听地址
listen = *, ::

# 认证设置
auth_mechanisms = plain login

# 邮件存储
mail_location = maildir:/var/mail/vhosts/%d/%n
mail_privileged_group = mail

# SSL 设置
ssl = required
ssl_cert = </etc/ssl/certs/mail.example.com.crt
ssl_key = </etc/ssl/private/mail.example.com.key
ssl_protocols = !SSLv2 !SSLv3
ssl_cipher_list = HIGH:!aNULL:!MD5

# IMAP 设置
service imap-login {
inet_listener imap {
port = 143
}
inet_listener imaps {
port = 993
ssl = yes
}
}

# POP3 设置
service pop3-login {
inet_listener pop3 {
port = 110
}
inet_listener pop3s {
port = 995
ssl = yes
}
}

# 认证服务
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
}

邮件故障排查

常见问题诊断

1. 邮件发送失败

# 检查 SMTP 服务状态
systemctl status postfix

# 查看邮件队列
postqueue -p

# 强制发送队列中的邮件
postqueue -f

# 查看日志
tail -f /var/log/mail.log

# 测试 SMTP 连接
telnet mail.example.com 25

2. 邮件被标记为垃圾邮件

检查项:

  • SPF 记录是否正确配置
  • DKIM 签名是否正常
  • DMARC 策略是否设置
  • 服务器 IP 是否在黑名单中
# 检查 SPF
dig txt example.com

# 检查 DKIM
dig txt default._domainkey.example.com

# 检查 DMARC
dig txt _dmarc.example.com

# 检查 IP 是否在黑名单
# 使用在线工具如: https://mxtoolbox.com/blacklists.aspx

3. 无法接收邮件

# 检查端口是否开放
netstat -tlnp | grep -E '25|143|110|993|995'

# 检查防火墙
iptables -L -n

# 检查 DNS MX 记录
dig mx example.com

# 检查邮件日志
grep -i "reject\|error\|fail" /var/log/mail.log

日志分析

Postfix 日志示例

# 正常发送
postfix/smtp[12345]: ABC123: to=<[email protected]>, relay=mail.example.org[192.0.2.1]:25, delay=1.2, delays=0.1/0.1/0.5/0.5, dsn=2.0.0, status=sent (250 OK)

# 被拒绝
postfix/smtp[12345]: ABC123: to=<[email protected]>, relay=none, delay=30, delays=0.1/0.1/30/0, dsn=4.4.1, status=deferred (connect to mail.example.org[192.0.2.1]:25: Connection timed out)

调试技巧

# 启用详细日志
postconf -e "debug_peer_list = example.org"
postconf -e "debug_peer_level = 3"
postfix reload

# 模拟发送邮件
sendmail -v [email protected] < test_email.txt

# 检查配置
postfix check

总结

电子邮件协议是互联网基础设施的重要组成部分:

SMTP:负责邮件的发送和传输,是邮件系统的"发件箱"。

  • 使用端口 25/587/465
  • 三阶段会话:连接建立、邮件传输、连接关闭
  • 支持 STARTTLS 加密和 SASL 认证

POP3:简单的邮件获取协议,适合单设备使用。

  • 使用端口 110/995
  • 下载并删除模式
  • 功能简单,适合存档

IMAP:功能强大的邮件管理协议,适合多设备同步。

  • 使用端口 143/993
  • 服务器端存储和同步
  • 支持文件夹、标志、搜索

邮件安全:SPF、DKIM、DMARC 共同防止邮件伪造和钓鱼。

  • SPF:验证发送服务器授权
  • DKIM:验证邮件签名
  • DMARC:定义验证失败后的处理策略

正确配置邮件服务器和安全机制,是保证邮件可靠送达和防范垃圾邮件的基础。

[!TIP] 想了解其他应用层协议?请看 DNS 域名系统HTTP 协议详解SSH、FTP 与 SFTP

练习

  1. 描述 SMTP 邮件发送的完整会话过程
  2. 比较 POP3 和 IMAP 的主要区别及各自适用场景
  3. 解释 SPF、DKIM、DMARC 三者的关系和工作原理
  4. 使用 telnet 手动发送一封测试邮件
  5. 配置一个简单的邮件服务器并测试邮件收发