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

Node.js服务器无法发送账户验证邮件问题排查求助

账户验证邮件无法送达的排查与修复方案

数据库中已成功创建新用户,但账户验证邮件始终无法送达。使用Handlebars作为邮件模板,相关代码如下:

sendEmail 工具函数代码

import nodeMailer from "nodemailer"
import handlebars from "handlebars"
import fs from "fs"
import path from "path"

export async function sendEmail(email, subject, payload,templates){
    try {
      // create reusable transporter object using the default SMTP transport
      const transporter = nodeMailer.createTransport({
        host: process.env.EMAIL_HOST,
        port: 465,
        secure: true,
        auth: {
          user: process.env.EMAIL_USERNAME,
          pass: process.env.EMAIL_PASSWORD, 
        },
      })
      transporter.verify().then(console.log).catch(console.error)
  
      const source = fs.readFileSync(path.join(__dirname,templates), "utf8");
      const compiledTemplate = handlebars.compile(source);
      
      const options = () => {
        return {
          from: process.env.FROM_EMAIL,
          to: email,
          subject: subject,
          html: compiledTemplate(payload),
        }
      }
  
      // Send email
      transporter.sendMail(options(), (error, info) => {

        if (error) {
          return error
        } else {
          console.log("Email sent successfully")
          return res.status(200).json({
            success: true,
            message:"Email sent successfully"
          })
        } 
        })
    } catch (error) {
      return error;
    }
  }

registerUser 控制器调用代码

export async function registerUser(req,res){

    try{
        const {email, password, userRole} = req.body;

    // Validation
  if (!email || !password ) {
    res.status(400).json({
        message:'Please include all fields'})
  }

  // Find if user already exists
  const userExists = await userModel.findOne({ email })

  if (userExists) {
    res.status(400).send({
        message:'User already exists'})
  }
  // CREATING USER

    /// function to generate accesstoken
    const tokens = jwt.sign({email},process.env.JWT_SECRET, {
        expiresIn: "1d"
       })
       
    const user = await userModel.create({
        email,
        password,
        userRole: "user",
        token:tokens
    })
    
    const link = `${process.env.BASE_URL}/user/verify/${user._id}/${user.token}`
    sendEmail(user.email,"Account Verification",
    {email: user.email,link: link,}, 
    "../templates/accountVerification.handlebars")
        

    if (user){
        res.status(200).json({
            message:'Registration Successful',
            user
        })

        
    }else{
        res.status(400).json({
            message:"Registration unsuccessful"
        })
    }
    }catch(err){
        console.log(err)
    }
    
}

核心问题排查与修复

1. 修复res未定义错误

sendEmail函数的transporter.sendMail回调中直接使用res.status(200),但该函数并未接收res参数,会触发运行时错误,中断邮件发送流程。

修复方案:将sendEmail改为Promise风格,避免在工具函数中直接操作响应对象,让调用方统一处理结果:

export async function sendEmail(email, subject, payload, templatePath) {
  try {
    const transporter = nodeMailer.createTransport({
      host: process.env.EMAIL_HOST,
      port: 465,
      secure: true,
      auth: {
        user: process.env.EMAIL_USERNAME,
        pass: process.env.EMAIL_PASSWORD,
      },
    })

    // 打印SMTP连接验证细节,排查连接问题
    const verifyResult = await transporter.verify()
    console.log("SMTP连接验证成功:", verifyResult)

    // 用绝对路径确保模板文件可被找到
    const absoluteTemplatePath = path.resolve(__dirname, templatePath)
    const source = fs.readFileSync(absoluteTemplatePath, "utf8")
    const compiledTemplate = handlebars.compile(source)

    const mailOptions = {
      from: process.env.FROM_EMAIL,
      to: email,
      subject: subject,
      html: compiledTemplate(payload),
    }

    // 改用await发送邮件,统一错误处理
    const info = await transporter.sendMail(mailOptions)
    console.log("邮件发送成功:", info.response)
    return { success: true, message: "Email sent successfully" }
  } catch (error) {
    console.error("邮件发送失败:", error)
    throw error // 抛出错误让调用方处理
  }
}

2. 正确调用sendEmail并处理错误

在registerUser中必须用await调用sendEmail,并捕获可能的错误,同时确认模板路径的有效性:

export async function registerUser(req, res) {
  try {
    const { email, password, userRole } = req.body

    // 提前返回避免嵌套
    if (!email || !password) {
      return res.status(400).json({ message: 'Please include all fields' })
    }

    const userExists = await userModel.findOne({ email })
    if (userExists) {
      return res.status(400).send({ message: 'User already exists' })
    }

    const token = jwt.sign({ email }, process.env.JWT_SECRET, { expiresIn: "1d" })
    const user = await userModel.create({
      email,
      password,
      userRole: "user",
      token
    })

    const link = `${process.env.BASE_URL}/user/verify/${user._id}/${user.token}`
    // await调用并处理邮件发送结果
    await sendEmail(
      user.email,
      "Account Verification",
      { email: user.email, link },
      "../templates/accountVerification.handlebars"
    )

    res.status(200).json({
      message: 'Registration Successful',
      user
    })
  } catch (err) {
    console.error("注册流程错误:", err)
    res.status(500).json({ message: "Registration failed due to internal error" })
  }
}

3. 排查SMTP配置问题

邮件无法送达最常见原因是SMTP配置错误:

  • 确认EMAIL_HOST是否正确(如Gmail为smtp.gmail.com,Outlook为smtp.office365.com);
  • 端口465对应secure: true,若使用587端口,需设置secure: false并添加requireTLS: true;
  • 确认EMAIL_USERNAME和EMAIL_PASSWORD有效性,部分邮箱(如Gmail)需使用应用专用密码而非普通密码;
  • 检查服务器是否允许出站SMTP连接(部分云服务商默认禁用25/465/587端口)。

4. 验证模板路径正确性

添加日志打印模板绝对路径,确认文件是否存在:

const absoluteTemplatePath = path.resolve(__dirname, templatePath)
console.log("模板文件路径:", absoluteTemplatePath) // 检查路径是否指向有效文件

内容的提问来源于stack exchange,提问作者Linda Pomaa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:17:09