很多开发者第一次给项目接邮件通知,多半会先找免费的 SMTP 服务。用一段时间后,常见的情况是邮件被丢进垃圾箱,或者月中免费额度用完,验证码突然发不出去。阿里云邮件推送(DirectMail)是更稳的选择。它有独立的发信通道,支持域名认证、退订管理和发送统计,验证码、订单通知、营销邮件都能走。这篇文章讲三件事:两种发信方式怎么选,上线前要配哪些 DNS 记录,以及一个订单通知的完整代码示例。

一、SMTP 和 API 怎么选
DirectMail 支持两种发信方式,区别主要在改造成本和返回信息的细致程度。
- SMTP:原来用的是别的邮件服务,就把 SMTP 地址、端口和账号换成阿里云的,代码基本不用动。适合老系统快速切换。
- API:调用阿里云 SDK 或 HTTP 接口。能拿到请求 ID 和错误码,批量发送和异步重试也更好做。
验证码和订单通知一般用 SMTP 就够。营销邮件,或者需要按错误码分别处理的场景,直接用 API。
二、上线前的三步准备
1. 用自己的子域名发信
发件域必须是你自己拥有的域名。建议专门拿一个子域名发信,比如 notice.example.com。这样它的信誉和主域名是分开的,DNS 记录也好管理。不要直接用主域名发营销邮件。一旦被标记为垃圾邮件,这个域名下的其他邮箱服务也会受连带影响。
2. 在 DNS 里加三条认证记录
在控制台添加发信域名后,需要到 DNS 解析里加记录:
- SPF:告诉收件方哪些服务器可以代表你的域名发信。
- DKIM:给邮件加数字签名,收件方据此判断内容在传输中有没有被改动。
- DMARC:规定验证失败的邮件怎么处理,放行、隔离还是拒收。刚开始可以设成 p=none,只监控不拦截,看过报告、确认正常后再收紧。
SPF 最容易出错。按 RFC 7208 的规定,同一个域名只能有一条 SPF 记录,写成两条会导致验证报错。常见情况是新接了一个第三方发信服务,又单独加了一条,结果邮件大量进垃圾箱,排查很久才发现。正确做法是合并成一条,把各个发信服务的 include 写在同一行里。
3. 分开建发信地址
在控制台新建发信地址,比如 no-reply@notice.example.com,并选好发信类型。验证码、订单通知这类触发邮件,和营销邮件最好使用不同的地址。营销邮件投诉率偏高时,触发类邮件不会跟着受影响。
三、示例:用 Python 通过 API 发送订单确认邮件
假设用户下单成功后,系统要自动发一封确认邮件,里面有订单号和收货地址。发送失败要记日志,并支持重试。下面的代码用阿里云官方 SDK(alibabacloud_dm20151123)实现这个需求。账号信息从环境变量读取,不写进代码。
import os
import time
from alibabacloud_dm20151123.client import Client
from alibabacloud_dm20151123 import models as dm_models
from alibabacloud_tea_openapi import models as open_api_models
def build_client():
# 从环境变量读取凭证,避免明文写入代码
config = open_api_models.Config(
access_key_id=os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"],
access_key_secret=os.environ["ALIBABA_CLOUD_ACCESS_KEY_SECRET"],
endpoint="dm.aliyuncs.com",
)
return Client(config)
def send_order_email(client, to_address, order_id, address, retries=3):
"""发送订单确认邮件,失败时按指数退避重试"""
html_body = (
"您好,您的订单已确认。
"
f"订单号:{order_id}
"
f"收货地址:{address}
"
)
request = dm_models.SingleSendMailRequest(
account_name="no-reply@notice.example.com",
address_type=1, # 0为随机账号,1为发信地址
reply_to_address=False,
subject="您的订单已确认",
html_body=html_body,
to_address=to_address,
from_alias="示例商城",
)
for attempt in range(1, retries + 1):
try:
response = client.single_send_mail(request)
print(f"发送成功,RequestId: {response.body.request_id}")
return True
except Exception as e:
print(f"第{attempt}次发送失败:{e}")
if attempt < retries:
time.sleep(2 ** attempt) # 指数退避:2s、4s
return False
if __name__ == "__main__":
client = build_client()
success = send_order_email(
client,
to_address="customer@example.com",
order_id="ORD20240001",
address="示例省示例市示例区示例路1号",
)
print("最终结果:", "成功" if success else "失败,需人工介入")
代码里有三点值得留意。凭证从环境变量读取,部署时交给容器或 CI 平台注入即可。失败后按指数退避重试,间隔 2 秒、4 秒,能避免短时间内连续请求撞上限流。每次返回的 RequestId 都要记进日志,投递出问题时,它是向阿里云提交工单的依据。
SDK 的包名和方法名会随版本变化。上线前请对照阿里云官方文档,并确认安装的版本与示例一致。
四、投递失败时按这个顺序查
接口返回成功但用户没收到,先别急着改代码,按下面的顺序排查,大多数问题能很快定位。
先看垃圾箱,再看 DNS
让测试用户确认邮件是否进了垃圾箱。然后用在线工具检查 SPF、DKIM、DMARC 的解析结果,逐条和控制台显示的值比对。控制台里的退信和投诉数据也要看。如果某个邮箱服务商的拦截特别多,通常要先降低发送频率,再检查邮件内容。
提示发信地址未验证
多数是 DNS 记录还没生效。解析变更有时要几分钟,有时要几小时。这时反复点击验证没有用,先核对主机记录和记录值,包括多余的空格、漏掉的点号、复制时被截断的部分。值和控制台完全一致后,再耐心等待生效。
触发限流
单个账号的发送频率和日发送量都有上限。批量发送时不要在循环里直接调接口,先把任务放进队列,按速率分批发出。
五、几条日常注意事项
- 验证码邮件保持简短,不要夹带营销链接,否则容易被判为垃圾邮件。
- 营销邮件必须有退订入口,收到退订请求要及时处理。这是合规要求,也能压低投诉率。
- 长期不打开、不点击的地址定期清理。名单质量上去了,发信信誉才比较好维持。
- 上线前用测试地址分别在 Gmail、Outlook、QQ 邮箱收一遍,看看展示效果,也看看有没有进垃圾箱。
六、小结
阿里云发信本身不复杂,最费时间的通常是域名认证和发信信誉。DNS 记录配齐,发信地址按业务分开,代码里做好凭证管理和失败重试,大多数中小项目就够用了。量级上来以后,再考虑队列发送、投递数据分析和多通道备份。