Skip to main content

RFC 5321 - 简单邮件传输协议 (Simple Mail Transfer Protocol)

发布日期: 2008年10月
状态: 互联网标准 (STD 10)
作者: J. Klensin
废止: RFC 2821
更新: RFC 1123


摘要 (Abstract)

本文档是互联网电子邮件传输的基本协议规范。它整合、更新并阐明了多个先前文档,使其大部分或全部内容过时。它涵盖了现代互联网的SMTP扩展机制和最佳实践,但不提供特定扩展的详细信息。虽然SMTP被设计为邮件传输和投递协议,但本规范还包含了对其作为"分离式用户代理"(split-UA)邮件阅读系统和移动环境的"邮件提交"协议使用的重要信息。


目录 (Table of Contents)

附录 (Appendices)


相关资源


快速参考 (Quick Reference)

SMTP基本概念

SMTP (Simple Mail Transfer Protocol, 简单邮件传输协议) 是互联网电子邮件传输的标准协议。它定义了邮件服务器之间如何传递邮件,是电子邮件系统的基石。

核心端口:

  • 端口25: SMTP服务器间通信
  • 端口587: 邮件提交 (MSA, 推荐使用STARTTLS)
  • 端口465: SMTPS (SMTP over SSL/TLS, 已弃用但仍广泛使用)

电子邮件系统架构

完整的邮件流程

发件人写邮件

┌─────────────────┐
│ 邮件客户端 │ (Outlook, Gmail, Thunderbird)
│ (MUA) │
└────────┬────────┘
│ SMTP (端口587, STARTTLS)

┌─────────────────┐
│ 发件服务器 │ smtp.sender.com
│ (MSA/MTA) │
└────────┬────────┘
│ SMTP (端口25)

[互联网]


┌─────────────────┐
│ 收件服务器 │ smtp.receiver.com
│ (MTA) │
└────────┬────────┘


┌─────────────────┐
│ 邮箱存储 │
│ (MDA) │
└────────┬────────┘
│ IMAP/POP3

┌─────────────────┐
│ 收件人读邮件 │
│ (MUA) │
└─────────────────┘

组件说明

组件全称作用
MUAMail User Agent邮件客户端(Outlook, Thunderbird)
MSAMail Submission Agent接收用户提交的邮件
MTAMail Transfer Agent转发邮件到目标服务器
MDAMail Delivery Agent将邮件投递到用户邮箱

SMTP vs IMAP/POP3

SMTP (发送邮件):
用户 → SMTP → 服务器 → SMTP → 目标服务器
发送 传输

IMAP/POP3 (接收邮件):
用户 ← IMAP/POP3 ← 服务器
下载/同步

SMTP会话详解

基本SMTP会话

客户端: smtp.sender.com              服务器: smtp.receiver.com
│ │
│ [建立TCP连接到端口25] │
│───────────────────────────────────────>│
│ │
│ 220 smtp.receiver.com ESMTP │
│<───────────────────────────────────────│
│ │
│ EHLO smtp.sender.com │
│───────────────────────────────────────>│
│ │
│ 250-smtp.receiver.com │
│ 250-SIZE 52428800 │ (50MB限制)
│ 250-8BITMIME │ (支持8位字符)
│ 250-PIPELINING │ (管道化)
│ 250-STARTTLS │ (TLS加密)
│ 250 HELP │
│<───────────────────────────────────────│
│ │
│ 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: Alice <[email protected]> │
│ To: Bob <[email protected]> │
│ Subject: Hello │
│ Date: Mon, 21 Dec 2024 10:00:00 +0000 │
│ │
│ Hi Bob, │
│ This is a test email. │
│ │
│ Best regards, │
│ Alice │
│ . │ (结束标记)
│───────────────────────────────────────>│
│ │
│ 250 2.0.0 Ok: queued as 12345 │
│<───────────────────────────────────────│
│ │
│ QUIT │
│───────────────────────────────────────>│
│ │
│ 221 2.0.0 Bye │
│<───────────────────────────────────────│
│ │
│ [关闭TCP连接] │

SMTP命令详解

基本命令

命令说明示例响应
EHLO扩展HELLO,识别客户端EHLO client.example.com250 + 扩展列表
HELOHELLO(旧版,向后兼容)HELO client.example.com250
MAIL FROM指定发件人MAIL FROM:<[email protected]>250 Ok
RCPT TO指定收件人RCPT TO:<[email protected]>250 Ok
DATA开始邮件内容DATA354 开始输入
QUIT结束会话QUIT221 Bye
RSET重置会话RSET250 Ok
VRFY验证地址VRFY user250/252/550
NOOP无操作(保持连接)NOOP250 Ok

扩展命令

命令说明用途
STARTTLS启动TLS加密保护传输安全
AUTHSMTP认证身份验证
SIZE声明邮件大小大小限制检查
PIPELINING管道化多个命令提高效率

响应代码

响应格式: XYZ 消息文本

X: 类别
2xx - 成功
3xx - 需要更多信息
4xx - 临时错误(可重试)
5xx - 永久错误(不可重试)

Y: 详细分类
x0x - 语法
x1x - 信息
x2x - 连接
x5x - 邮件系统

Z: 具体细节

常见响应码

代码说明含义
220Service ready服务准备就绪
221Closing connection关闭连接
250OK请求成功
354Start mail input开始输入邮件内容
421Service not available服务不可用(临时)
450Mailbox unavailable邮箱暂时不可用
451Local error本地错误(临时)
452Insufficient storage存储空间不足
500Syntax error语法错误
501Syntax error in parameters参数语法错误
502Command not implemented命令未实现
503Bad sequence命令顺序错误
550Mailbox unavailable邮箱不可用(永久)
551User not local用户不在本地
552Exceeded storage超过存储限制
553Mailbox name invalid邮箱名称无效
554Transaction failed事务失败

SMTP认证(AUTH)

为什么需要认证?

早期SMTP问题:
任何人都可以连接到服务器发送邮件
→ 垃圾邮件泛滥!

现代SMTP:
必须先认证身份,才能发送邮件
→ 防止滥用

SMTP AUTH机制

客户端                                服务器
│ │
│ EHLO client.example.com │
│────────────────────────────────────>│
│ │
│ 250-AUTH PLAIN LOGIN │
│ 250-AUTH CRAM-MD5 │
│<────────────────────────────────────│
│ │
│ AUTH PLAIN │
│────────────────────────────────────>│
│ │
│ 334 (Base64挑战) │
│<────────────────────────────────────│
│ │
│ [Base64(用户名\0密码)] │
│────────────────────────────────────>│
│ │
│ 235 Authentication OK │
│<────────────────────────────────────│

AUTH方法

1. PLAIN(明文,需TLS保护)

import base64

username = "[email protected]"
password = "secret123"

# 格式: \0username\0password
auth_string = f"\0{username}\0{password}"
auth_base64 = base64.b64encode(auth_string.encode()).decode()

# 发送
# AUTH PLAIN
# [等待334]
# auth_base64

2. LOGIN(明文,需TLS保护)

客户端: AUTH LOGIN
服务器: 334 VXNlcm5hbWU6 (Base64: "Username:")
客户端: YWxpY2U= (Base64: "alice")
服务器: 334 UGFzc3dvcmQ6 (Base64: "Password:")
客户端: c2VjcmV0MTIz (Base64: "secret123")
服务器: 235 Authentication successful

3. CRAM-MD5(安全,已过时)

import hmac
import hashlib
import base64

def cram_md5_auth(username, password, challenge):
# 解码挑战
challenge_decoded = base64.b64decode(challenge)

# 计算HMAC-MD5
digest = hmac.new(
password.encode(),
challenge_decoded,
hashlib.md5
).hexdigest()

# 构建响应
response = f"{username} {digest}"
return base64.b64encode(response.encode()).decode()

# 使用
# 服务器: 334 <challenge>
# 客户端: cram_md5_auth("alice", "secret123", challenge)

STARTTLS加密

为什么需要加密?

未加密的SMTP:
┌──────┐ 明文 ┌──────┐
│客户端│──────>│服务器│
└──────┘ 可监听 └──────┘

[攻击者可以看到]
- 用户名和密码
- 邮件内容
- 收发件人

使用STARTTLS:
┌──────┐ TLS加密 ┌──────┐
│客户端│═════════>│服务器│
└──────┘ 无法解密 └──────┘

STARTTLS流程

客户端                                服务器
│ │
│ EHLO client.example.com │
│────────────────────────────────────>│
│ │
│ 250-STARTTLS │
│<────────────────────────────────────│
│ │
│ STARTTLS │
│────────────────────────────────────>│
│ │
│ 220 Ready to start TLS │
│<────────────────────────────────────│
│ │
│ [TLS握手] │
│<═══════════════════════════════════>│
│ │
│ [现在所有通信都加密] │
│ │
│ EHLO client.example.com (重新EHLO) │
│═══════════════════════════════════=>│
│ │
│ AUTH PLAIN ... │
│═══════════════════════════════════=>│

实现示例

Python实现(发送邮件)

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

class SMTPClient:
def __init__(self, host, port=587):
self.host = host
self.port = port
self.smtp = None

def connect(self, username, password):
"""建立连接并认证"""
print(f"连接到 {self.host}:{self.port}")

# 创建SMTP对象
self.smtp = smtplib.SMTP(self.host, self.port)

# 开启调试(查看SMTP会话)
self.smtp.set_debuglevel(1)

# 发送EHLO
self.smtp.ehlo()

# 启动TLS加密
if self.smtp.has_extn('STARTTLS'):
print("启动TLS加密...")
self.smtp.starttls()
self.smtp.ehlo() # 重新EHLO

# 认证
print("进行认证...")
self.smtp.login(username, password)
print("✅ 认证成功")

def send_simple(self, from_addr, to_addr, subject, body):
"""发送简单文本邮件"""
msg = MIMEText(body, 'plain', 'utf-8')
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject

self.smtp.send_message(msg)
print(f"✅ 邮件已发送到 {to_addr}")

def send_html(self, from_addr, to_addr, subject, html_content):
"""发送HTML邮件"""
msg = MIMEMultipart('alternative')
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject

# 添加纯文本版本(备用)
text_part = MIMEText('请使用支持HTML的邮件客户端查看此邮件', 'plain', 'utf-8')
msg.attach(text_part)

# 添加HTML版本
html_part = MIMEText(html_content, 'html', 'utf-8')
msg.attach(html_part)

self.smtp.send_message(msg)
print(f"✅ HTML邮件已发送到 {to_addr}")

def send_with_attachment(self, from_addr, to_addr, subject, body, filename):
"""发送带附件的邮件"""
msg = MIMEMultipart()
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject

# 邮件正文
msg.attach(MIMEText(body, 'plain', 'utf-8'))

# 附件
with open(filename, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())

# 编码附件
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
f'attachment; filename={filename}'
)
msg.attach(part)

self.smtp.send_message(msg)
print(f"✅ 带附件的邮件已发送到 {to_addr}")

def close(self):
"""关闭连接"""
if self.smtp:
self.smtp.quit()
print("连接已关闭")

# 使用示例
client = SMTPClient('smtp.gmail.com', 587)

try:
# 连接并认证
client.connect('[email protected]', 'your_password')

# 发送简单邮件
client.send_simple(
from_addr='[email protected]',
to_addr='[email protected]',
subject='测试邮件',
body='这是一封测试邮件。'
)

# 发送HTML邮件
html = """
<html>
<body>
<h1>你好!</h1>
<p>这是一封<strong>HTML</strong>邮件。</p>
</body>
</html>
"""
client.send_html(
from_addr='[email protected]',
to_addr='[email protected]',
subject='HTML测试',
html_content=html
)

# 发送带附件的邮件
client.send_with_attachment(
from_addr='[email protected]',
to_addr='[email protected]',
subject='带附件的邮件',
body='请查看附件。',
filename='document.pdf'
)

finally:
client.close()

Node.js实现(Nodemailer)

const nodemailer = require('nodemailer');

// 创建传输器
const transporter = nodemailer.createTransporter({
host: 'smtp.gmail.com',
port: 587,
secure: false, // 使用STARTTLS
auth: {
user: '[email protected]',
pass: 'your_password'
},
// 显示SMTP日志
debug: true,
logger: true
});

// 验证连接
transporter.verify((error, success) => {
if (error) {
console.error('SMTP连接失败:', error);
} else {
console.log('✅ SMTP服务器准备就绪');
}
});

// 发送简单邮件
async function sendSimpleEmail() {
const info = await transporter.sendMail({
from: '"Alice" <[email protected]>',
to: '[email protected]',
subject: '测试邮件',
text: '这是一封测试邮件。',
html: '<p>这是一封<strong>测试</strong>邮件。</p>'
});

console.log('邮件ID:', info.messageId);
console.log('预览URL:', nodemailer.getTestMessageUrl(info));
}

// 发送带附件的邮件
async function sendWithAttachment() {
const info = await transporter.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: '带附件的邮件',
text: '请查看附件。',
attachments: [
{
filename: 'document.pdf',
path: './document.pdf'
},
{
filename: 'image.png',
path: './image.png'
}
]
});

console.log('✅ 邮件已发送:', info.messageId);
}

// 批量发送
async function sendBulk() {
const recipients = [
'[email protected]',
'[email protected]',
'[email protected]'
];

for (const recipient of recipients) {
await transporter.sendMail({
from: '[email protected]',
to: recipient,
subject: '批量通知',
text: `你好 ${recipient},这是一封群发邮件。`
});

console.log(`✅ 已发送到 ${recipient}`);

// 延迟避免被限速
await new Promise(resolve => setTimeout(resolve, 1000));
}
}

// 使用模板
async function sendTemplate(userName, orderNumber) {
const html = `
<html>
<body>
<h1>订单确认</h1>
<p>亲爱的 ${userName},</p>
<p>您的订单 <strong>#${orderNumber}</strong> 已确认。</p>
<p>感谢您的购买!</p>
</body>
</html>
`;

await transporter.sendMail({
from: '[email protected]',
to: userName,
subject: `订单 #${orderNumber} 确认`,
html: html
});
}

// 执行
sendSimpleEmail().catch(console.error);

常见邮件服务器配置

Gmail

SMTP服务器: smtp.gmail.com
端口: 587 (STARTTLS) 或 465 (SSL/TLS)
认证: 必需

注意: 需要启用"应用专用密码"或"不太安全的应用访问"

Outlook/Hotmail

SMTP服务器: smtp-mail.outlook.com
端口: 587 (STARTTLS)
认证: 必需

Yahoo

SMTP服务器: smtp.mail.yahoo.com
端口: 587 (STARTTLS) 或 465 (SSL/TLS)
认证: 必需

自建服务器(Postfix)

# 安装Postfix
sudo apt install postfix

# 基本配置 /etc/postfix/main.cf
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
relayhost =
mynetworks = 127.0.0.0/8, 192.168.1.0/24
inet_interfaces = all
inet_protocols = all

# SMTP AUTH支持
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes

# TLS加密
smtpd_tls_cert_file = /etc/ssl/certs/mail.crt
smtpd_tls_key_file = /etc/ssl/private/mail.key
smtpd_use_tls = yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# 重启服务
sudo systemctl restart postfix

反垃圾邮件技术

SPF (Sender Policy Framework)

作用: 验证发件人IP是否被授权

DNS记录示例:
example.com. IN TXT "v=spf1 ip4:192.0.2.0/24 include:_spf.google.com ~all"

含义:
- 允许192.0.2.0/24网段
- 允许Google的服务器
- ~all: 软失败(其他来源可疑)

DKIM (DomainKeys Identified Mail)

作用: 给邮件添加数字签名

流程:
1. 发件服务器用私钥签名邮件
2. 在邮件头部添加DKIM-Signature
3. 收件服务器用公钥验证签名

DNS记录:
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCS..."

邮件头部:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=default;
h=from:to:subject:date;
bh=base64encodedBodyHash;
b=base64encodedSignature

DMARC (Domain-based Message Authentication)

作用: SPF和DKIM的策略层

DNS记录:
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]"

策略:
- p=none: 仅监控
- p=quarantine: 可疑邮件进垃圾箱
- p=reject: 拒绝未通过验证的邮件

报告:
- rua: 汇总报告地址
- ruf: 失败报告地址

性能优化

1. 连接池

class SMTPPool:
def __init__(self, host, port, max_connections=5):
self.host = host
self.port = port
self.max_connections = max_connections
self.available = []
self.in_use = set()

def get_connection(self, username, password):
"""获取一个连接"""
if self.available:
conn = self.available.pop()
elif len(self.in_use) < self.max_connections:
conn = smtplib.SMTP(self.host, self.port)
conn.starttls()
conn.login(username, password)
else:
# 等待可用连接
while not self.available:
time.sleep(0.1)
conn = self.available.pop()

self.in_use.add(conn)
return conn

def release_connection(self, conn):
"""释放连接"""
self.in_use.remove(conn)
self.available.append(conn)

def close_all(self):
"""关闭所有连接"""
for conn in list(self.available) + list(self.in_use):
conn.quit()

2. PIPELINING

# 启用管道化(减少往返次数)
smtp = smtplib.SMTP('smtp.example.com')
smtp.ehlo()

if 'PIPELINING' in smtp.esmtp_features:
print("✅ 服务器支持管道化")

# 可以连续发送多个命令
smtp.docmd('MAIL FROM:<[email protected]>')
smtp.docmd('RCPT TO:<[email protected]>')
smtp.docmd('RCPT TO:<[email protected]>')
smtp.docmd('RCPT TO:<[email protected]>')

3. 批量发送优化

async def send_bulk_async(recipients, message):
"""异步批量发送"""
import asyncio
import aiosmtplib

async def send_one(recipient):
async with aiosmtplib.SMTP(
hostname='smtp.example.com',
port=587,
use_tls=True
) as smtp:
await smtp.login('user', 'password')
await smtp.send_message(message)

tasks = [send_one(r) for r in recipients]
await asyncio.gather(*tasks)

故障排查

1. 测试SMTP连接

# 使用telnet测试
telnet smtp.example.com 25

# 或使用openssl (STARTTLS)
openssl s_client -connect smtp.example.com:587 -starttls smtp

# 手动SMTP会话
EHLO client.example.com
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
DATA
Subject: Test
From: [email protected]
To: [email protected]

Test message
.
QUIT

2. 检查邮件头部

查看完整邮件头部可以看到:
- 传递路径 (Received headers)
- SPF/DKIM/DMARC结果
- 延迟信息
- 服务器跳转

示例:
Received: from smtp.sender.com (192.0.2.1)
by smtp.receiver.com (192.0.2.2)
with ESMTPS id 12345
for <[email protected]>;
Mon, 21 Dec 2024 10:00:00 +0000 (UTC)
Authentication-Results: smtp.receiver.com;
spf=pass smtp.mailfrom=sender.com;
dkim=pass header.d=sender.com;
dmarc=pass header.from=sender.com

3. 常见问题

问题: 连接超时
原因:
- 防火墙阻止端口25/587/465
- ISP阻止出站SMTP
解决: 检查防火墙规则,使用VPN

问题: 认证失败
原因:
- 用户名/密码错误
- 未启用"不太安全的应用"
- 需要OAuth2认证
解决: 检查凭据,启用应用专用密码

问题: 邮件进垃圾箱
原因:
- 缺少SPF/DKIM/DMARC
- IP信誉差
- 内容触发垃圾邮件过滤器
解决: 配置反垃圾邮件技术,预热IP

问题: 发送限制
原因:
- 超过每日/每小时限制
- 被识别为垃圾邮件
解决: 分批发送,使用专业邮件服务

总结

SMTP是电子邮件系统的基石:

  • : 准确可靠的邮件传输
  • : 清晰的协议流程
  • : 优雅的扩展机制

核心特性

  1. 📧 简单文本协议
  2. 🔐 支持认证和加密
  3. 🌐 全球互通
  4. 📨 可靠的消息传递
  5. 🛡️ 反垃圾邮件机制

最佳实践

  • ✅ 始终使用STARTTLS或SSL/TLS
  • ✅ 实施SPF、DKIM和DMARC
  • ✅ 使用连接池提高性能
  • ✅ 实施速率限制
  • ✅ 监控邮件传递状态

现代挑战

  • 垃圾邮件过滤
  • 送达率优化
  • IP信誉管理
  • 大规模发送

相关RFC

  • RFC 5321: SMTP(本文档)
  • RFC 5322: 邮件格式
  • RFC 3501: IMAP
  • RFC 1939: POP3
  • RFC 6531: 国际化邮箱

返回RFC列表