You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 14:34:55