NodeMailer跨模块发邮件异常:PayAsYouLike请求遭连接拒绝
问题描述
我的网站需发送两类邮件:
- PayAsYouLike模块:向申请用户发送随机生成的密码
- GetInTouch模块:将联系表单内容发送至我的个人邮箱
两类邮件共用email.js路由文件。使用Thunder Client测试时,两类请求均能正常发送邮件;但前端发起调用时,GetInTouch模块请求100%成功,PayAsYouLike模块请求却100%失败,控制台报ERR_CONNECTION_REFUSED错误。服务器日志仅能打印到sendMail函数内的Inside sendMail, but before transporter.sendMail call!,说明错误出在transporter.sendMail(mailOptions)调用环节。尝试过拆分路由、复用GetInTouch的fetch逻辑等方法,问题依旧。
附上email.js路由代码:
const express = require("express"); const router = express.Router(); require("dotenv").config(); const nodeMailer = require("nodemailer"); router.route("/").post((req, res) => { const form = req.body; let mailOptions = {}; console.log(form); const transporter = nodeMailer.createTransport({ service: "gmail", host: "smtp.gmail.com", port: 587, secure: false, auth: { user: process.env.USER, pass: process.env.APP_PASSWORD, }, }); if ("password" in form) { const passwordDate = new Date(form.date); const passwordValidity = new Date(passwordDate.getTime() + 172800000); const options = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true }; mailOptions = { from: process.env.USER, to: form.email, subject: "Your PASSWORD is here!", html: `Dear ${ form.name || form.email }, <br><br>Your PASSWORD: <h3 style="display:inline;">${ form.password }</h3><br><i>Expiry:</i> <h4 style="display:inline;">${passwordValidity.toLocaleDateString('en-US', options)}</h4>`, }; } else { mailOptions = { from: process.env.USER, to: "karangour@gmail.com", subject: "GetInTouch form", html: `FROM: ${form.name}<br>EMAIL: ${form.email}<br>SUBJECT: ${form.subject}<br>MESSAGE: ${form.message}`, }; } console.log("Mail options prepared:", mailOptions); const sendMail = async (transporter, mailOptions) => { try { console.log('Inside sendMail, but before transporter.sendMail call!') await transporter.sendMail(mailOptions); console.log("Email has been sent successfully!!"); res.json({ status: "Success", message: "Email sent successfully", }); } catch (error) { console.log(error); res.json({ status: "Error", message: "Failed to send email", error: error.message, }); } }; sendMail(transporter, mailOptions); }); module.exports = router;
排查与解决方案
1. 核对前端请求数据一致性
对比前端PayAsYouLike模块发送的form数据与Thunder Client的测试数据,重点检查:
date字段的格式:前端可能传入了非标准日期字符串(如非ISO格式),导致后端new Date(form.date)生成无效日期,进而破坏邮件内容格式,触发SMTP服务器拒绝连接email字段的合法性:确认前端传入的收件人邮箱格式符合规范,避免因无效邮箱触发SMTP校验失败- 请求头配置:确保前端请求的
Content-Type为application/json,与Thunder Client测试配置一致
2. 增强后端错误捕获与日志
在transporter.sendMail调用前添加参数校验,同时打印完整错误信息定位问题:
// 在sendMail调用前插入 // 校验收件人邮箱格式 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(mailOptions.to)) { console.error("无效收件人邮箱:", mailOptions.to); return res.status(400).json({ status: "Error", message: "无效邮箱地址" }); } // 校验date字段有效性 if ("password" in form && isNaN(new Date(form.date).getTime())) { console.error("无效日期格式:", form.date); return res.status(400).json({ status: "Error", message: "无效日期格式" }); } // 修改catch块日志 catch (error) { console.log("完整错误详情:", error); res.json({ status: "Error", message: "邮件发送失败", error: error.message, fullError: JSON.stringify(error) }); }
3. 优化Transporter创建逻辑
将Transporter的创建移至路由外部,全局复用连接,避免每次请求创建新SMTP连接导致的资源耗尽:
const express = require("express"); const router = express.Router(); require("dotenv").config(); const nodeMailer = require("nodemailer"); // 全局创建并复用Transporter const transporter = nodeMailer.createTransport({ service: "gmail", host: "smtp.gmail.com", port: 587, secure: false, auth: { user: process.env.USER, pass: process.env.APP_PASSWORD, }, }); // 提前校验Transporter配置 transporter.verify((error, success) => { if (error) console.error("Transporter校验失败:", error); else console.log("Transporter已就绪"); }); router.route("/").post(async (req, res) => { const form = req.body; let mailOptions = {}; console.log(form); // ... 原有mailOptions生成逻辑不变 ... console.log("邮件配置已准备:", mailOptions); try { console.log('进入sendMail,即将调用transporter.sendMail!') await transporter.sendMail(mailOptions); console.log("邮件发送成功!!"); res.json({ status: "Success", message: "邮件发送成功", }); } catch (error) { console.log("完整错误详情:", error); res.json({ status: "Error", message: "邮件发送失败", error: error.message, fullError: JSON.stringify(error), }); } }); module.exports = router;
4. 检查Gmail SMTP配置
- 确认Gmail账号已开启2FA,且使用的
APP_PASSWORD正确有效 - 检查Gmail发送配额,避免因请求频率过高触发限制
内容的提问来源于stack exchange,提问作者Karan Gour
相关产品推荐
相关产品推荐

