Adgora
指南 5分钟阅读 982个字

用 Node.js 工作队列可靠发送 SMTP 邮件

将 SMTP 通知移出请求流程,使用持久化任务、幂等键、工作进程租约和分类重试,降低请求超时与重复发送风险。

Adgora
在此页面0%

    活动审核、余额变更或账户安全提醒,不应该让 Node.js 请求一直等待邮件服务器响应。如果邮件中继速度慢,面向用户的操作可能会超时;如果应用重试请求,同一封邮件还可能被发送两次。因此,讨论 Node.js SMTP 邮件队列怎么实现时,重点应是将通知与业务事件一起记录下来,再由后台工作进程通过 SMTP 发送。

    将邮件发送移出请求处理流程

    以加密广告网络为例,假设某个广告活动的状态从待审核变为已批准。应用应尽快提交这一状态变更,并处理 транзакційні та маркетингові повідомлення(事务性和营销消息),而不应等 SMTP 连接完成后才向广告主返回响应。

    将邮件任务存入数据库表或持久化队列,并与状态变更置于同一事务中。工作进程读取待处理任务,再通过邮件中继发送邮件。这样可以将应用可用性与邮件投递分开:邮件中继暂时不可用时,通知可以延迟发送,但不会撤销审核结果,也不会阻塞控制台操作。

    如果要将 Node.js SMTP 中继引入现有服务,应把工作进程的边界视为首要设计决策,而不是邮件库。Email & messaging platform 可以为事务性邮件提供 SMTP 连接,但应用仍需决定何时创建邮件、如何处理重试,以及投递状态不确定时该怎么做。

    为每个业务事件创建一条通知任务

    应在底层事件提交时创建任务,而不是每次加载页面或用户重试 API 请求时才生成任务。为任务分配稳定的事件键,例如广告活动 ID、状态变更和版本号的组合。对该键设置唯一性约束,避免重复请求生成多封待发送的审核通知邮件。

    实用的任务记录应包含收件人、模板标识符、渲染邮件所需的最少数据、尝试次数、下次尝试时间和状态。不要在其中存储 SMTP 凭据或不必要的账户信息。如果收件地址可能在发送前发生变化,应明确规定任务使用事件发生时记录的地址,还是账户当前已验证的地址。

    确保邮件内容对应真实的状态变更。工作进程不应根据过时的队列负载推断审核结果。发送敏感提醒前,可以核对当前事件版本或读取相关记录。这样可以避免广告活动刚被暂停,就向客户告知其已获批准。

    在工作进程中配置 Node.js 邮件中继

    使用 Nodemailer 等维护良好的 SMTP 客户端,并从工作进程的环境变量中读取连接信息,不要将其提交到源代码管理系统。以下示例使用常见的邮件提交端口;请根据账户提供的凭据调整端口和 TLS 设置。

    const nodemailer = require("nodemailer");
    
    const port = Number(process.env.SMTP_PORT || 587);
    
    const transporter = nodemailer.createTransport({
      host: process.env.SMTP_HOST,
      port,
      secure: port === 465,
      requireTLS: port !== 465,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS
      }
    });
    
    async function sendCampaignNotice(message) {
      return transporter.sendMail({
        from: process.env.MAIL_FROM,
        to: message.to,
        subject: message.subject,
        text: message.text,
        html: message.html
      });
    }

    发件人地址应符合你的发信配置要求,并为工作进程使用独立凭据,不要重复使用管理员登录凭据。不要在日志中打印完整邮件内容、密码或身份验证响应。启动时进行验证可以帮助发现无效配置,但不能保证特定收件人之后一定会接受邮件。

    重试临时故障,避免邮件风暴

    Node.js 后台任务邮件发送与重试,应根据故障类型制定策略,而不是以相同频率重试所有错误。临时连接故障或服务器提示服务繁忙时,可以逐步延长间隔后重试。凭据无效、收件人地址格式错误以及其他持久性配置或地址问题,需要排查,而不是频繁重试。设置最大尝试次数,并将达到上限的任务移至可供运维人员检查的状态。

    使用工作进程租约或同等的任务认领机制,防止两个 Node.js 进程同时发送同一条待处理任务。工作进程认领任务时,应记录租约到期时间;如果进程崩溃,其他工作进程可以在租约到期后重新认领。租约时长应超过常规发送超时时间;如果邮件渲染或投递可能耗时更久,则应延长租约。

    要理解如何避免 SMTP 邮件重复发送,首先要考虑 SMTP 无法为你的应用提供真正的“恰好一次”保证。邮件中继可能已经接受邮件,但连接却在工作进程收到确认前中断。此时重试可能导致重复发送;而在中继接受邮件前就标记为已发送,则可能导致邮件丢失。使用稳定的事件键防止重复创建任务,限制重试次数,并接受这一小概率的不确定情况并不总能自动解决。

    按可控的步骤部署变更

    在切换生产流量前,先从非生产环境发送测试任务,确认工作进程能够通过身份验证、正确渲染预期内容,并记录成功和失败状态。然后先为一种低风险通知类型启用工作进程。对比业务事件数、队列任务数和已完成发送数;积压不断增加,可能意味着工作进程或配置存在问题,可在迁移关键提醒前及时发现。

    • 在部署过程中,确保 Web 应用和工作进程使用相同的消息架构。
    • 启动多个工作进程实例前,先确保任务创建具备幂等性。
    • 监控队列等待时间、重试次数、永久性故障和工作进程健康状况。
    • 为审核过的故障保留手动重新处理流程,不要不加判断地重发所有失败任务。

    工作进程稳定后,应从请求处理程序中移除直接 SMTP 发送逻辑,确保这些邮件只有一条受控发送路径。如果使用 Email & messaging platform 作为中继,它负责 SMTP 交接;队列、事件规则、重试策略和运维审核仍由你负责。当账户事件必须快速记录、即使邮件短暂中断也不能受影响时,这种分工很有用,但它不能取代监控,也不能代替明确的延迟通知处理策略。

    分享这篇文章

    觉得有用吗?把它发送给购买或销售流量的人。

    下一步

    准备好将其付诸实践了吗?

    在 Adgora 启动活动或变现您的流量 — 加密支付、实时统计、无锁定。

    此页面回答的问题