Nodemailer运行一段时间后停止发邮件无响应无报错该如何排查?
故障原因分析与修复方案
你遇到的故障不是Nodemailer的已知问题,是代码实现存在资源泄漏和请求处理缺陷导致的,具体问题和修复方法如下:
- 重复创建SMTP传输实例导致连接资源泄漏
你将nodemailer.createTransport初始化逻辑写在了/send接口的请求回调内,每收到一次发送请求就会新建一个SMTP连接实例,旧实例使用完后没有主动销毁,长时间运行会逐步占满服务器的文件句柄、SMTP连接数配额,后续请求无法新建连接就会陷入无限等待。
修复方案:将transporter初始化逻辑移到接口外层,全局仅初始化一次,同时开启内置连接池控制最大连接数,参考代码:// 全局初始化,仅运行一次 let transporter = nodemailer.createTransport(smtpTransport({ pool: true, // 开启连接池 maxConnections: 5, // 最大连接数按需调整 name: "myname", host: "myhost", port: 465, secure: true, auth: { user: "info@myhost.nl", pass: "123456", }, tls: { rejectUnauthorized: false, }, logger: true, debug: true, })); // 监听连接层面的报错,避免遗漏日志 transporter.on('error', err => { console.log('SMTP连接异常:', err); }) - 错误场景未返回响应导致请求堆积
你在transporter.sendMail的错误回调中仅打印了错误日志,没有给客户端返回响应、结束当前请求,出错的请求会一直挂在服务端占用连接资源,长时间运行后服务的请求连接队列被占满,新请求就会无响应。
修复方案:错误分支也正常返回响应结束请求,参考代码:transporter.sendMail(mailOptions, (error, info) => { if (error) { console.log(error); // 错误场景返回响应 return res.status(500).render("contact", { msg: "邮件发送失败" }); } res.render("contact", { msg: "Email has been sent" }); console.log("Email has been sent"); }); - 同步读取模板文件阻塞事件循环
每次请求都调用fs.readFileSync同步读取邮件模板,同步操作会阻塞Node.js事件循环,长时间运行或者并发量升高时会导致服务响应卡顿。
修复方案:启动时一次性读取模板缓存到内存,不需要每次请求都读:// 全局预读取模板 const emailTemplate = fs.readFileSync('employee.html').toString(); // 接口内直接替换token const output = emailTemplate.replace("${token}", token);
辅助定位方法
如果需要进一步确认根因,可以做以下排查:
- 运行时监控服务器的文件句柄占用、TCP连接数变化,确认是否存在资源持续上涨不释放的情况,Linux可使用
lsof -p 进程ID、netstat -anp | grep 465命令查看,Windows可通过资源监视器查看对应进程的网络连接、句柄占用。 - 给Express添加全局超时中间件,避免请求无限挂起,同时超时的时候可以打印日志定位问题:
app.use((req, res, next) => { res.setTimeout(10000, () => { console.log('请求超时:', req.path, req.body); return res.status(408).send('请求超时'); }) next(); })
内容的提问来源于stack exchange,提问作者SJ19
相关产品推荐
相关产品推荐

