DigitalOcean上MERN应用Nodemailer报PLAIN认证凭据缺失错误求助
DigitalOcean部署MERN应用Nodemailer发送邮件报错解决
问题背景
我在DigitalOcean应用平台部署了一个MERN应用,用户注册时会收到验证邮箱的欢迎邮件;未验证的用户登录后,可在个人资料页点击按钮重新发送验证邮件。我使用Nodemailer通过个人Comcast账号发送邮件,该功能在本地环境及Render.com平台均正常,但在DigitalOcean上抛出如下错误:
Error: Missing credentials for "PLAIN" [mat-scout] [2023-11-08 02:05:49] at SMTPConnection._formatError (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:790:19) [mat-scout] [2023-11-08 02:05:49] at SMTPConnection.login (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:444:38) [mat-scout] [2023-11-08 02:05:49] at /workspace/node_modules/nodemailer/lib/smtp-transport/index.js:272:32 [mat-scout] [2023-11-08 02:05:49] at SMTPConnection.<anonymous> (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:213:17) [mat-scout] [2023-11-08 02:05:49] at Object.onceWrapper (node:events:627:28) [mat-scout] [2023-11-08 02:05:49] at SMTPConnection.emit (node:events:513:28) [mat-scout] [2023-11-08 02:05:49] at SMTPConnection._actionEHLO (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:1347:14) [mat-scout] [2023-11-08 02:05:49] at SMTPConnection._processResponse (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:969:20) [mat-scout] [2023-11-08 02:05:49] at SMTPConnection._onData (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:755:14) [mat-scout] [2023-11-08 02:05:49] at TLSSocket.SMTPConnection._onSocketData (/workspace/node_modules/nodemailer/lib/smtp-connection/index.js:193:44) { [mat-scout] [2023-11-08 02:05:49] code: 'EAUTH', [mat-scout] [2023-11-08 02:05:49] command: 'API' [mat-scout] [2023-11-08 02:05:49] }
尝试添加text: "Hello World"字段后,错误依然存在,以下是我的mail.js代码:
import nodemailer from "nodemailer"; import path from "path"; import { fileURLToPath } from "url"; import { generateTemplate } from "../mail/template.js"; const generateMailTransporter = () => { let transport = nodemailer.createTransport({ host: "smtp.comcast.net", port: 587, secure: false, auth: { user: process.env.EMAIL, pass: process.env.EMAIL_PASS, }, }); return transport; }; export const sendVerificationEmail = async (token, link, profile) => { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const transport = generateMailTransporter(); const { firstName, email } = profile; const welcomeMessage = `Hi ${firstName}, welcome to ! There are so many features available to verified users. Use the given OTP (One-Time-Password) to verify your email.`; transport.sendMail({ to: email, from: process.env.VERIFICATION_EMAIL, subject: "Welcome to MatScout", text: "hello world", html: generateTemplate({ title: "Welcome to MatScout", message: welcomeMessage, logo: "cid:logo", banner: "cid:welcome", link, btnTitle: "Verify Your Email", }), attachments: [ { fileName: "logo.png", path: path.join(__dirname, "../mail/logo.png"), cid: "logo", }, { fileName: "welcome.png", path: path.join(__dirname, "../mail/welcome.png"), cid: "welcome", }, ], }); }; export const sendForgotPasswordLink = async (link, profile) => { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const transport = generateMailTransporter(); const { email } = profile; const message = `We just received a request to reset your password. Use the link beloow to create a new password. <br><br> If you did not send this request, it means someone is trying to access your account and you can disregard this email.`; async function main() { let info = await transport.sendMail({ to: email, from: process.env.VERIFICATION_EMAIL, subject: "Reset Password Link", text: "hello world", html: generateTemplate({ title: "Forgot Password", message, logo: "cid:logo", banner: "cid:forget_password", link, btnTitle: "ResetPassword", }), attachments: [ { fileName: "logo.png", path: path.join(__dirname, "../mail/logo.png"), cid: "logo", }, { filename: "forget_password.png", path: path.join(__dirname, "../mail/forget_password.png"), cid: "forget_password", }, ], }); } main().catch(console.error); }; export const sendPassResetSuccessEmail = async (firstName, email) => { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const transport = generateMailTransporter(); const message = `Dear ${firstName}, your password has just been reset successfuly. You can now sign in with your new password.`; transport.sendMail({ to: email, from: process.env.VERIFICATION_EMAIL, subject: "Password Reset Successfully!", text: "hello world", html: generateTemplate({ title: "Password Reset Successfully", message, logo: "cid:logo", banner: "cid:forget_password", link: process.env.SIGN_IN_URL, btnTitle: "Sign In", }), attachments: [ { fileName: "logo.png", path: path.join(__dirname, "../mail/logo.png"), cid: "logo", }, { filename: "forget_password.png", path: path.join(__dirname, "../mail/forget_password.png"), cid: "forgot_password", }, ], }); };
解决方案
1. 检查DigitalOcean环境变量配置
- 登录DigitalOcean控制台,进入应用的「Settings」-「Environment Variables」页面,确认
EMAIL、EMAIL_PASS、VERIFICATION_EMAIL三个变量已正确填写,无拼写错误、大小写错误或多余空格。 - 变量修改后需重启应用,确保新配置生效。
2. 验证SMTP连接配置
Comcast的SMTP服务器需要TLS加密,可在 transporter 配置中显式添加TLS相关设置(测试用,生产环境建议保留默认证书验证):
const generateMailTransporter = () => { let transport = nodemailer.createTransport({ host: "smtp.comcast.net", port: 587, secure: false, auth: { user: process.env.EMAIL, pass: process.env.EMAIL_PASS, }, tls: { rejectUnauthorized: false // 临时关闭证书验证,测试通过后移除 } }); return transport; };
3. 确认Comcast账号权限
- Comcast可能限制第三方SMTP访问,需登录Comcast账号,检查是否开启了「允许不太安全的应用访问」(若有该选项)。
- 若账号开启了2FA,需生成专用应用密码,替换
EMAIL_PASS环境变量中的原账号密码。
4. 增加环境变量加载验证
在generateMailTransporter函数中添加日志,确认环境变量是否正确加载:
const generateMailTransporter = () => { // 临时日志,上线后删除 console.log('加载的EMAIL:', process.env.EMAIL); console.log('EMAIL_PASS是否存在:', !!process.env.EMAIL_PASS); let transport = nodemailer.createTransport({ host: "smtp.comcast.net", port: 587, secure: false, auth: { user: process.env.EMAIL, pass: process.env.EMAIL_PASS, }, }); return transport; };
5. 修复代码中的小问题
- 原代码中附件字段
fileName拼写错误,应为filename(nodemailer标准字段名),虽然不影响当前认证错误,但会导致附件加载异常:// 错误写法 fileName: "logo.png" // 正确写法 filename: "logo.png" - 邮件发送函数需添加
await和错误捕获,避免静默失败:export const sendVerificationEmail = async (token, link, profile) => { // ... 其他代码 ... try { await transport.sendMail({/* 邮件配置 */}); } catch (err) { console.error('发送验证邮件失败:', err); } };
内容的提问来源于stack exchange,提问作者Stephen Scott Moore
相关产品推荐
相关产品推荐

