资讯动态

邮件与短信发送的工程化实战:通道选型、送达率与稳定性设计

发布时间:2026/10/16 11:09:04 来源:尧图企业网站定制
做线上触达开发的团队基本都会碰到同一个问题需要发邮件又需要发短信但这两个通道的能力边界、成本模型和技术链路完全不是一回事。我见过太多项目把“邮件和短信发送”做成两个孤立的工具类各自接各自的渠道等到用户被重复打扰、账单日送达率暴跌、短信模板因内容不合规被驳回来的时候才发现问题并不是“接个接口”那么简单。这篇就围绕我自己在几个项目里沉淀下来的经验把邮件和短信发送这件事从头到尾拆开讲一遍包括通道定位、投递链路、服务商接入、工程化设计以及上线前后真正会踩的坑。这篇内容适合两类读者一类是刚接手通知模块、准备把发送能力做完整的后端开发另一类是已经在用某个服务商SDK、但发现送达率、回执、重试、限流这些环节始终靠手动维护的团队。全文不会依赖某个具体品牌所有方案都能平移到你自己的架构里。1. 邮件与短信的定位之争两类通道究竟该给谁用先说结论邮件和短信不是互相替代的关系它们是两种定位完全不同的触达通道。邮件适合“内容多、时效要求宽松、成本敏感”的场景短信适合“必须马上让用户知道、内容短、愿意按条付费”的场景。如果一开始没把这个边界划清楚后面所有的技术选型都会被带着走偏。我习惯把系统消息先按这三个等级归类通知提醒类例如账号有异动、订单状态变化、发货提醒。这类消息“用户必须尽快知道”以短信为主邮件做兜底内容补充。内容推送类例如周报、账单明细、产品使用建议。这类消息“用户可晚点看、可忽略”以邮件为主短信只发送摘要加提醒。系统告警类例如服务宕机、数据异常、监控触发。这类消息必须短信因为值夜班的人不会随时盯着邮箱但手机在身边。这样划分之后通道选择和业务价值直接挂钩而不是拍脑袋觉得“邮件便宜就全走邮件”“短信送达率高就全走短信”。1.1 不同通道的核心成本模型短信的成本是硬成本。每一条都要按条计费量大了之后一个月几万甚至几十万的短信费非常常见。我在一个日活中等规模的项目里见过光是验证码短信一个月就烧掉不少预算后来不得不引入“行为风控”来减少恶意请求。邮件则几乎是边际成本趋近于零的通道但它的隐性成本在运营侧退订管理、垃圾箱过滤、发送域名信誉维护这些都需要专人关注并不比对接一个短信API轻松。所以成本上真正合理的做法是能用邮件说明白的绝不用短信打扰用户必须立刻触达的才动用短信。这个原则听上去简单但很多团队是在月度账单出来之后才想起做这件事。1.2 “验证码该走哪条通道”就是一张试金石验证码是一个特别典型的场景。它可以走邮件但用户体验非常糟糕用户注册时收不到邮件验证码第一反应就是骂产品客服介入后一查邮件要么进了垃圾箱要么延迟了两分钟。验证码这类强时效消息优先级高于一切必须短信优先邮件只能作为备用通道。反过来说一封周报邮件没必要做成短信。用户每周收到一条“您的周报已生成请点击查看”意思不大反而消耗预算、增加投诉风险。消息到达的“刚好感”才是通道选择里最重要的指标。2. 邮件投递不是把信扔进邮筒就行域名认证与送达率实战很多开发第一次接邮件发送时思路是“找一个SMTP服务器配置好账号密码调用发信函数”。走通一两次确实没问题但等真正开始给真实用户发批量邮件就会发现送达率一下子掉到惨不忍睹的水平。问题基本出在两个地方没有做发信域名的身份认证以及没有控制发信节奏。2.1 自建SMTP还是用邮件服务商API自建SMTP服务器的核心优势是量足够大之后单封成本非常低。只要硬件资源到位发信量可以做到很高。劣势也很明显IP信誉需要养。新的IP一开始发信邮箱服务商不认识你很容易把大量邮件扔进垃圾箱甚至直接拒收。IP信誉一旦掉下去恢复周期是按周甚至按月计算的这个过程比写代码痛苦多了。中小型业务我推荐直接用邮件服务商API。原因不在于花多少钱而在于服务商已经帮你把退信处理、投诉统计、IP池预热这些脏活都做完了。你只需要做两件事域名验证和模板适配。如果你所在地区的业务量极大也可以走“服务商API为主、自建SMTP兜底”的混合方案但我不建议一上来就自建运维成本远高于想象。2.2 SPF、DKIM、DMARC是投递的基本门槛邮件服务商接入后第一件事不是写发信代码而是配置DNS记录。如果不配置或者配置漏掉其中一项发出的邮件极难进收件箱。这三项的作用SPF声明“哪些IP允许代发我这个域名的邮件”防止别人拿你的域名伪造发件人。DKIM给邮件做一段数字签名收件方可以校验这封邮件确实来自该域名且内容未被篡改。DMARC明确告诉收件方前面的SPF和DKIM校验如果失败该怎么处理这封邮件。配置示例SPF记录TXT vspf1 include:spf.examplemail.com ~all DKIM记录TXTselector由邮件服务商生成 vDKIM1; krsa; pMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC... DMARC记录TXT vDMARC1; pquarantine; ruamailto:dmarcexample.com这里有个细节容易被忽略SPF里的~all和-all是有区别的。~all表示“如果不是授权IP邮件可能有问题但别直接拒绝”-all表示“直接拒绝”。刚开始切流量的阶段建议用~all等确认没有第三方系统在冒用你的域名发信之后再考虑切换成-all。DMARC策略同样建议从pnone开始配合报告观察一段时间的邮件来源再逐步收紧到quarantine或者reject。2.3 发信节奏和退信处理是被多数人忽略的变量即使SPF、DKIM、DMARC配置全对也不能保证送达率一直稳定。决定送达率的另外两个变量是收件人列表质量和发送节奏。收件人列表质量指的是邮箱地址是否为有效、活跃的地址。如果列表里残留大量拼写错误、停机、已注销的老地址每次批量发送都会产生大量退信尤其是hard bounce。服务商会把这视为“你在给无效地址发信”直接降低你的域名信誉。我在一个新的通知项目里做过一次实测导入一批历史注册用户时没有过滤掉无效邮箱直接跑了批量发送结果一小时内有大量退信反馈回来当天下午再发正常用户邮件几乎全部进了垃圾箱。后来修复动作是加了一层退信统计逻辑每个地址连续三次hard bounce就自动标记为无效从后续批次中剔除。同时把发送节奏从“一次性全部发完”改成“按批次发送每批之间间隔数秒到几分钟”的平滑模式。两个动作叠加之后送达率恢复到了正常水平。2.4 邮件模板的样式克制写技术方案时很容易把邮件模板当成普通HTML页面来做但邮件客户端和浏览器是完全不同的渲染环境。大部分邮箱客户端不支持JavaScript部分CSS属性也支持得很差比如Flex布局在不少老牌客户端里会直接失效。稳妥的做法是表格布局加内联样式。尽量少用外部CSS少用图片做核心信息承载因为很多客户端默认不加载远程图片。模板里的变量填充建议用模板引擎而不是字符串拼接可以避免用户输入的内容里带HTML标签导致整个模板错乱。3. 短信发送的接入细节签名、模板、回执与通道切换短信发送表面上比邮件简单其实坑一点都不少。最核心的三个环节是资质审核、异步回执和通道容灾。任何一个处理不好线上都会出现问题。3.1 签名与模板审核是前置条件短信不是随手发一串字符就能成功的。国内短信通道要求消息必须带签名签名通常是【品牌名】或【企业名】申请签名需要主体资质材料。内容部分走模板审核模板里允许包含变量但整个模板的文案结构必须经过服务商和运营商侧审核。运营上有个常见麻烦业务方喜欢直接在代码里写死短信内容换一个运营活动就要改代码重新走审核。建议把短信模板统一收口到配置中心或者线上管理系统里由运营或者有权限的开发者提交修改审核通过后才能被发送服务引用。模板变量也要在系统里声明清楚避免随意拼接导致内容不合规。3.2 模板变量与内容填充的几个具体坑审核过了不代表发送就顺利。我见过下面这几类问题反复出现变量位超长。模板预留了8个字符业务方传了一个几十位的订单号结果短信被网关截断或报错。解决方式是在发送前对每个变量做长度校验。变量内容携带换行符或特殊字符。从后台输入框复制出来的文本常带着换行拼接进短信后内容被截断部分网关会把这类请求判定为格式异常。双模板变量填错位置。订单号和优惠券码传反用户收到一条完全看不懂的短信这个基本属于接口参数映射错误但只要多一个校验层就能拦住大部分情况。一个简单实用的方案是发送服务内部维护一套变量元数据声明每个变量的最大长度、是否允许特殊字符、是否必须为数字提交发送请求时先做一次白名单校验不合格的直接返回业务错误而不是把脏数据发给通道。3.3 异步回执决定了“发送成功”的真正含义短信发送必须理解清楚提交状态和最终状态的区别。客户端调用服务商接口提交成功只代表服务商已经把消息转给运营商不代表用户手机已经收到。最终是否送达依赖短信中心异步返回的状态回执。实际对接中常见的回执状态回执状态含义建议处理方式DELIVRD用户已收到更新发送记录为终态UNDELIV未送达多为停机、空号或信号异常可重试一次仍失败则进人工队列REJECTD被运营商拦截检查内容合规性不建议反复重发TIMEOUT等待回执超时一般保守处理以发送方记录为准回执这块最容易出的问题是开发只写了提交接口没做回执接收接口导致数据库里一堆“发送中”状态永远不更新。运营要核对短信账单时两边数据对不上就很难查。上线前必须把回执回调打通并且保证回调处理接口是幂等的因为服务商在网络异常时可能会重复推送同一回执。3.4 多通道接入和动态切换短信服务商不是无状态虚拟化它也有自己的通道容量和稳定性上限。大促、活动、验证码高峰期单一通道很容易出现提交成功但回执延迟的情况。我建议至少接入两家短信服务商做一个轻量级的健康度评估池。健康度评估可以从三个指标入手提交成功率、回执到达平均延迟、最近五分钟的失败率。当其中一个通道的失败率超过阈值发送服务自动把流量切到另一个通道。这个机制在平时是静默的通常不会触发但到了关键时刻它是整个通知系统最后一道防线。另外要给每条短信请求设置超时时间不能让发送请求无限阻塞发送线程。某些网关看起来正常的提交可能在服务商内部排队等待回执的时间远超过预期这种场景下超时后进入重试逻辑反而更合适。4. 把发送能力收敛成一个稳定引擎队列、重试与幂等设计邮件和短信这两个通道如果做成散落在各业务模块的工具类最直接的问题就是重复建设、重复打扰、无法统一治理。更好的选择是做一个独立的通知发送服务对上暴露统一的提交接口对下统一管理邮件和短信通道。4.1 统一消息对象的设计所有业务方调用的提交接口都传一个结构化的消息对象。字段设计参考{ message_id: uuid-20260101-xxxx, channel: sms, template: order_notify, to: [13800000000], params: { order_no: 20260101001, user_name: 张三 }, priority: high, schedule_time: 2026-01-01T10:00:0008:00 }每个字段都有它的作用message_id是幂等关键channel决定走邮件还是短信通道template对应双方各自维护的模板标识params是模板变量填充数据。业务方不需要关心具体通道的API细节也不需要关心邮件SPF或短信签名这些底层内容发送服务统一处理。接口设计上有意识地做成“幽灵提交”风格会更好就是只要任务被受理就返回成功后续真正的发送和重试由后台任务完成。这样做的好处是接口响应快、并发能力高业务方不会被通道抖动拖垮。4.2 发送状态机与重试策略发送记录的状态建议拆成更细的状态机pending任务已提交等待调度sending已投递给通道sent提交通道成功但短信回执或邮件投递结果未确认success用户实际收到短信有DELIVRD回执邮件有服务商送达反馈failed最终失败expired超过有效期限不再尝试重试不能盲目重试。标准的做法是指数退避加抖动。比如第一次失败后1分钟重试第二次5分钟第三次30分钟最多三次。要加上随机抖动避免同一批失败任务在同一个时间点集中重试把通道打挂。更重要的是全局熔断假设服务商通道故障同一时间如果有十万条消息进入重试队列系统会自我加压。用一个失败率阈值判断超过阈值就暂停通道发送等健康度恢复再放量。很多时候系统的崩溃不是外部通道挂掉造成的而是内部重试风暴把整个服务拖垮的。4.3 幂等和防重是短信场景的硬需求短信按条计费重复发送等于白白扣钱加消耗用户信任。幂等实现其实不复杂发送记录表里给message_id加唯一索引提交任务时先插入记录重复提交会在数据库唯一约束层面被拦截。另一种常见情况是消息队列的at-least-once投递模式下同一任务被消费两次如果发送逻辑没有幂等校验用户就会收到两条一模一样的验证码短信。这个问题的根子在架构而非代码落库时做一层去重很有必要。除了消息级幂等还需要用户级频控。每个手机号每分钟接收短信的上限、每天接收短信的上限、同一用户验证码发送间隔都应在发送服务里配置好。这些规则用一张配置表维护不需要改代码就能调整。4.4 定时任务和调度语义日常业务里“账单日给用户发邮件”“活动开始时批量发短信”都是定时触发的。发送服务需要支持schedule_time把定时任务落入待调度队列由后台扫描任务到期的消息并下发。这里有个容易忽略的问题定时任务不能只依赖扫描一次就发出因为通道可能刚好在抖动最好在扫描触发后把任务再放进实时队列走一次标准的发送流程保证能复用重试机制。5. 上线前后我踩过的坑和一份可以直接抄的检查清单这部分我尽量把写过悔过的经验写详细一点希望能帮大家少走弯路。第一个印象特别深的邮件坑一个项目更换邮件服务商后只改了API Key没有重新做域名验证。结果服务商要求把新的DKIM记录添加到DNS我漏了这一步测试邮件看着能发出去但全部进垃圾箱。当时的排查顺序是怀疑内容模板怀疑发送频率最后才想到查DNS记录。后来我做了一个工具脚本上线前自动检查当前域名在DNS里是否有完整的SPF、DKIM、DMARC记录并把检查结果作为发布流程的硬性门槛。第二个邮件坑是模板渲染太“现代”。我刚开始写邮件模板时用了比较多的CSS选择器和Flex布局拿主流浏览器预览一切正常。真实的邮箱客户端里渲染出来完全错位用户反馈“邮件看起来像没做完”。从那之后我给自己定了一条规矩邮件模板只用table布局和内联样式并对排版效果做跨客户端测试。短信侧的坑更直接。有一次生产环境的短信模板变量长度限制配了个8但实际传参时没有做校验导致触发告警消息的订单号被截断用户收到的内容语义完全变了。排查下来发现是开发阶段只测了模板本身没测变量长度的边界情况。后来模板系统就按“模板变量元数据优先”的方式重做了所有发送请求在进入发送前都会先做一次参数校验。还有一个短信坑是关于测试号码。当时把某个同事的真实手机号当成线上测试号一不小心触发了批量发送直接把该号码送进了运营商的频率限制池正常验证码都被拦截了半天。从那以后测试号码统一用一个灰度白名单体系管理不在白名单里的号码一律禁止触发批量发送。5.1 上线前的检查清单我每次发布或者新接入一个发送方案之前会在本地跑一遍这张清单照着打勾缺一项就先不上线DNS记录完整SPF、DKIM、DMARC均已添加并用公开检测工具能查到记录邮件模板已在内联样式方式下验证主流邮箱客户端的渲染效果均正常短信签名和模板已通过服务商审核模板标识在配置中与代码一致接收回执的接口已上线且用测试号码实际触发了一条短信并确认回执状态回填正确对同一message_id重复提交测试确定不会产生重复短信息费配置了用户级频控测试号码在频控规则内的发送行为符合预期至少接入了两个短信通道并验证过手动切换开关能正常工作发送失败率、队列积压长度、通道超时率都已接好监控告警且告警联系人是实际在线的人。5.2 最后再分享一个排查习惯刚开始接触消息发送系统的时候我处理线上问题习惯一上来就翻业务日志结果经常查到一半才发现是通道侧的问题非常浪费时间。后来我慢慢养成了一个固定的排查顺序先看今天的送达率趋势和回执状态分布确认到底是邮件送不进收件箱还是短信回执迟迟不到再决定继续查代码还是直接联系服务商。这个习惯帮我省下了很多无效排查。邮件送达率突然下降我会先检查域名配置是否被动过再检查列表里是否混入了大量无效地址短信DELIVRD比例下降我会先看当前用的通道是否还健康再去看模板有没有被改过。先把“通道本身”这个问题排除掉再回到应用代码层面找问题逻辑会清晰很多。邮件和短信发送这件事做到能发并不难难的是发得准、发得稳、发得可控。希望这篇内容能给你提供一个完整的参考框架。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑