SMTP 配置教程:从零开始搭建邮件发送系统

SMTP(Simple Mail Transfer Protocol)是互联网上发送邮件的标准协议。无论是网站注册验证、密码重置还是订单通知,背后都是 SMTP 在工作。本文介绍两种邮件发送方案:使用第三方邮件服务(推荐)和自建邮件服务器。

一、SMTP 基础

1.1 SMTP 工作原理

发件人 → MUA(邮件客户端)
         ↓ SMTP
       MTA(邮件传输代理)
         ↓ SMTP
       MTA(接收方)
         ↓ POP3/IMAP
       MUA → 收件人
  • MUA(Mail User Agent):邮件客户端(Outlook、Thunderbird)
  • MTA(Mail Transfer Agent):邮件传输代理(Postfix、Sendmail)
  • MDA(Mail Delivery Agent):邮件投递代理

1.2 SMTP 端口

端口 加密方式 说明
25 无/STARTTLS 传统端口,很多 ISP 已屏蔽
465 SSL/TLS SMTPS,推荐使用
587 STARTTLS 提交端口,推荐使用
2525 非标准 部分服务商提供的备用端口

建议使用 587(STARTTLS)或 465(SSL)端口发送邮件,25 端口常被 ISP 和云服务商屏蔽。

1.3 一次真实的 SMTP 会话

swakstelnet 就能手工发一封测试邮件,这比任何教程都直观。一个典型的会话长这样:

swaks --to [email protected] --from [email protected] \
  --server smtp.sendgrid.net:587 --tls \
  --header "Subject: Hello" --body "SMTP works!"

服务器与客户端之间的对话分四步:EHLO(打招呼并协商能力,如 STARTTLS)、MAIL FROM(声明发件人)、RCPT TO(声明收件人)、DATA(发送正文,以单独的 . 结束)。每一步收到 250 即成功,收到 550 通常是收件人不存在或反垃圾策略拒绝。看懂这段对话,遇到"邮件发不出去"时就能判断是连接、认证还是被拒收,而不必靠猜。

二、使用第三方邮件服务(推荐)

大多数应用不需要自建邮件服务器,使用第三方服务更简单、可靠。

2.1 通用 SMTP 配置

// PHP PHPMailer 示例
use PHPMailer\PHPMailer\PHPMailer;

$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host       = 'smtp.sendgrid.net';     // SMTP 服务器
$mail->SMTPAuth   = true;
$mail->Username   = 'apikey';                // 用户名(SendGrid 固定为 apikey)
$mail->Password   = 'SG.xxxxx';              // API Key
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port       = 587;

$mail->setFrom('[email protected]', 'Your App');
$mail->addAddress('[email protected]');
$mail->Subject = '验证您的邮箱';
$mail->Body    = '请点击链接验证您的邮箱地址:https://...';
$mail->send();

2.2 各服务商 SMTP 配置

# Python smtplib 示例 - SendGrid
import smtplib
from email.mime.text import MIMEText

msg = MIMEText('邮件内容')
msg['Subject'] = '测试邮件'
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'

with smtplib.SMTP('smtp.sendgrid.net', 587) as server:
    server.starttls()
    server.login('apikey', 'SG.xxxxx')
    server.send_message(msg)

各服务商 SMTP 参数:

服务商 服务器 端口 认证方式
SendGrid smtp.sendgrid.net 587/465 API Key
Mailgun smtp.mailgun.org 587/465 登录名+密码
Resend smtp.resend.com 587/465 API Key
Amazon SES email-smtp.region.amazonaws.com 587/465 SMTP 凭证
Mailjet in-v3.mailjet.com 587/465 API Key

2.3 送达率三件套:SPF / DKIM / DMARC

服务商配置好后,能不能进收件箱,还取决于域名侧的三个 DNS 记录。SPF 声明"谁允许用你的域名发信",DKIM 用公钥签名验证邮件确实来自你的服务器,DMARC 告诉收件方验证失败时怎么办。以 SendGrid 为例,典型配置是:

yourdomain.com.  TXT  "v=spf1 include:sendgrid.net ~all"
sg._domainkey.yourdomain.com.  TXT  "k=rsa; p=MIGfMA0...(SendGrid 提供的公钥)"
_dmarc.yourdomain.com.  TXT  "v=DMARC1; p=none; rua=mailto:[email protected]"

建议先用 p=none 观察一段时间的报告,确认没有合法邮件失败后,再逐步收紧到 p=quarantinep=reject。这一步很多新手会跳过,结果就是发送成功但全部进垃圾箱。完整细节可参考SPF/DKIM/DMARC 配置。

2.4 应用程序框架配置

// Node.js + Nodemailer
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  host: 'smtp.sendgrid.net',
  port: 587,
  auth: {
    user: 'apikey',
    pass: process.env.SENDGRID_API_KEY,
  },
});

await transporter.sendMail({
  from: '"App Name" <[email protected]>',
  to: '[email protected]',
  subject: '密码重置',
  html: `<p>请点击链接重置密码:<a href="${resetLink}">重置密码</a></p>`,
});
// Laravel 配置 (.env)
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=SG.xxxxx
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"

三、自建邮件服务器(高级)

3.1 使用 Postfix + Dovecot

适用于需要完全控制邮件系统的场景:

# Ubuntu 安装 Postfix
apt update && apt install postfix dovecot-core dovecot-imapd

# 配置 Postfix
postconf -e 'myhostname = mail.yourdomain.com'
postconf -e 'mydomain = yourdomain.com'
postconf -e 'myorigin = $mydomain'
postconf -e 'inet_interfaces = all'
postconf -e 'mynetworks = 127.0.0.0/8'
postconf -e 'smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem'
postconf -e 'smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key'
postconf -e 'smtpd_use_tls = yes'

# 重启服务
systemctl restart postfix
systemctl restart dovecot

3.2 自建 vs 第三方

维度 自建邮件服务器 第三方邮件服务
成本 仅服务器费用 按量或包月
送达率 较低(需预热 IP) 高(共享 IP 池信誉好)
控制权 完全控制 受限于服务商
维护成本 高(需监控、防攻击) 低(服务商维护)
可扩展性 需自行扩展 自动扩展
隐私 数据在自己服务器 数据在第三方

结论:除非有特殊需求(数据合规、极大量邮件),否则建议使用第三方邮件服务。

3.3 如果坚持自建:IP 预热与反向解析

自建服务器最大的门槛不是装软件,而是"让互联网信任你"。新 IP 的发送信誉是零,直接大批量发信基本都会进垃圾箱,需要按"每天少量递增"的方式预热 IP,通常持续数周。同时要保证三条:IP 有正确的反向 DNS(PTR 记录指向 mail.yourdomain.com)、SPF/DKIM/DMARC 全部就位、25 端口出站不被机房封禁(很多云厂商默认封锁 25 端口,需提交工单申请解封)。这三件事不做好,Postfix 装得再熟练,发出去的邮件也只是石沉大海。

四、邮件发送最佳实践

事务邮件与营销邮件的区别

两类邮件的发送策略完全不同。事务邮件(注册验证、密码重置、订单通知)是"用户预期内"的邮件,收件人会主动寻找,送达要求高但量通常不大;营销邮件(促销、周报、召回)量大、用户可能反感,对频率和退订机制要求严格。实践中建议:事务邮件走高信誉通道(可用独立域名),营销邮件单独用一个子域(如 marketing.yourdomain.com)并严格限流,避免营销邮件拉低整个域名信誉、连累事务邮件也进垃圾箱。

4.1 发送限制

服务商 每日上限 每秒上限
SendGrid 免费版 100 封 10 封
SendGrid Essentials 50,000 封 根据套餐
Mailgun Foundation 50,000 封 根据套餐
自建服务器 取决于服务器 需自行控制

4.2 错误处理

// 邮件发送错误处理
async function sendEmailWithRetry(emailData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await transporter.sendMail(emailData);
      return result;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // 指数退避
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
      console.log(`重试第 ${attempt} 次...`);
    }
  }
}

4.3 队列处理

对于大量邮件,使用消息队列异步发送:

// 使用 Bull 队列处理邮件
const Queue = require('bull');
const emailQueue = new Queue('email', redisConfig);

// 添加任务
emailQueue.add({
  to: '[email protected]',
  subject: '验证您的邮箱',
  html: '<p>请点击链接验证...</p>',
});

// 处理任务
emailQueue.process(async (job) => {
  await transporter.sendMail(job.data);
});

五、常见问题排查

问题 可能原因 解决方案
连接超时 SMTP 端口被防火墙/ISP 屏蔽 尝试使用 587 或 465 端口
认证失败 API Key 或密码错误 检查 API Key 权限
被拒收(550) IP 信誉低或未配置 SPF/DKIM 配置邮件认证,预热 IP
进入垃圾箱 内容或信誉问题 优化内容,检查发送频率
发送速率限制 超出服务商限制 使用队列控制发送速率

六、总结

对于大多数应用,推荐使用第三方邮件服务(SendGrid/Mailgun/Resend)的 SMTP,既简单又可靠。只需配置好 SPF/DKIM/DMARC 认证,就能获得良好的送达率。自建邮件服务器只适合有特殊需求(数据合规、每月百万级邮件量)的场景。无论使用哪种方案,邮件发送队列、错误处理和监控都是必须的组成部分。

参考:https://datatracker.ietf.org/doc/html/rfc5321https://www.spf-dkim-dmarc.org/https://docs.sendgrid.com/for-developers/sending-email/sender-identity