Node.js调用nodemailer发邮件时,错误日志未打印的原因排查
问题场景
使用无效邮箱测试邮件发送功能时,明明收到了投递失败的退信,但Node.js控制台没有输出任何catch块中的错误日志,仅打印了helper - sendEmail和OTP email being processed。相关代码如下:
主方法代码
// some code try { await sendOtpEmail(candidate.email, otp, reqHost) console.log('OTP email being processed'); } catch(error) { console.log('register-update-apply sendOtpEmail error:', error) throw 'OTP Email Error:' + error }
sendOtpEmail方法代码
async function sendOtpEmail(email, otp, reqHost) { // prepare email try{ await sendEmail({ to: email, subject: appName + ' Verification OTP', html: message }); } catch(error) { console.log('register-update-apply sendOtpEmail error:', error) } }
sendEmail方法代码
async function sendEmail({ to, subject, html}) { console.log('helper - sendEmail') const copyMailServerOptions = JSON.parse(JSON.stringify(config.hpMailServerOptions)); const transporter = nodemailer.createTransport(copyMailServerOptions); let sendEmail = true; const xFrom = copyMailServerOptions.auth.user transporter.verify(function(error, success) { if (error) { sendEmail = false; console.log('email transporter error', error); throw 'email transporter error: ' + error } else { // console.log('no email sending error'); } }); if(sendEmail) { try{ await transporter.sendMail({ xFrom, to, subject, html }); } catch(error){ console.log('transporter.sendMail to: ' + to + '. Error: ' + error) } } }
原因分析
错误被下层捕获但未向上传递
sendEmail方法中,transporter.sendMail的错误被自身catch块捕获后仅打印日志,没有重新抛出;sendOtpEmail捕获到错误后同样只打印不抛出,导致主方法的try/catch完全感知不到错误,自然不会触发自身的catch逻辑。transporter.verify的异步回调错误无法被捕获
transporter.verify使用了回调模式,而非promise+await,回调中的throw语句属于独立调用栈,无法被外层try/catch捕获。同时回调是异步执行的,代码会直接跳过回调逻辑执行if(sendEmail)分支,此时sendEmail仍为true,会继续执行sendMail操作。退信是邮件服务器的延迟反馈
你收到的退信是邮件服务器后续返回的投递失败通知,并非Nodemailer发送请求时同步返回的错误,所以sendMail调用本身可能已经成功(邮件提交给服务器),不会触发catch块。
修复方案
1. 改造transporter.verify为promise+await模式
Nodemailer的verify方法支持promise调用,无需回调:
async function sendEmail({ to, subject, html}) { console.log('helper - sendEmail') const copyMailServerOptions = JSON.parse(JSON.stringify(config.hpMailServerOptions)); const transporter = nodemailer.createTransport(copyMailServerOptions); const xFrom = copyMailServerOptions.auth.user try { // 改用promise+await,错误会被外层try/catch捕获 await transporter.verify(); await transporter.sendMail({ xFrom, to, subject, html }); } catch(error) { console.log('transporter operation error: ' + error); // 重新抛出错误,让上层方法感知 throw error; } }
2. 确保错误向上传递
修改sendOtpEmail方法,捕获错误后重新抛出,让主方法的try/catch能处理:
async function sendOtpEmail(email, otp, reqHost) { // prepare email try{ await sendEmail({ to: email, subject: appName + ' Verification OTP', html: message }); } catch(error) { console.log('register-update-apply sendOtpEmail error:', error) // 重新抛出错误 throw error; } }
3. 处理邮件服务器退信通知
若要捕获后续的投递失败(即你收到的退信),需要配置邮件服务器的回调(如SMTP DSN),或者定期查询投递状态,这部分不属于Nodemailer的同步错误捕获范畴,需要额外业务逻辑处理。
内容的提问来源于stack exchange,提问作者hemant

