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

使用Nodemailer发送邮件成功却出现ERR_HTTP_HEADERS_SENT错误求助

解决Nodemailer发送邮件时出现的ERR_HTTP_HEADERS_SENT错误

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)

问题原因

你遇到的这个错误核心是多次向客户端发送HTTP响应,具体问题点:

  1. 路由逻辑中重复发送响应:保存订阅邮箱后,你先调用res.status(200).json(...)返回了一次结果,之后又执行return res.status(200).send(link)再次发送响应,触发重复响应错误。
  2. sendEmail函数错误处理响应:函数内部尝试调用res.status(200).json(...),但该函数并未传入res对象(会导致未定义错误),即使传入也会再次触发响应发送,加剧问题。
  3. sendEmail是async函数,但使用了回调版的transporter.sendMail,导致await sendEmail(...)无法真正等待邮件发送完成,时序逻辑混乱。

修复步骤

1. 重构sendEmail为Promise风格

将回调改为Promise实现,让async/await能正确工作,并且不在函数内部处理响应,统一交给路由层处理:

const sendEmail = async (email, subject, payload, template) => {
  try {
    const transporter = nodemailer.createTransport({
      service: "gmail",
      auth: {
        user: process.env.EMAIL_USERNAME,
        pass: process.env.EMAIL_PASSWORD,
      },
    });

    const options = {
      from: process.env.FROM_EMAIL,
      to: email,
      subject: subject,
      html: template,
    };

    // 使用Promise版sendMail,移除回调逻辑
    const info = await transporter.sendMail(options);
    return { success: true, info };
  } catch (error) {
    throw error;
  }
};

2. 修复路由中的重复响应问题

调整路由逻辑,确保所有操作(保存邮箱+发送邮件)完成后只发送一次响应:

router.post("/", async (req, res, next) => {
  try {
    if (!req.body.newsletterEmail) {
      return next(createError(400, "Please enter your email"));
    } else if (!isEmail(req.body.newsletterEmail)) {
      // 移除重复的!req.body.newsletterEmail判断,已在前序逻辑校验过
      return next(createError(400, "Please enter a valid email address"));
    }

    const checkEmail = await SubscribedEmails.findOne({ newsletterEmail: req.body.newsletterEmail });
    if (checkEmail) {
      return next(createError(400, "Email is already subscribed"));
    }

    // 创建并保存订阅邮箱
    const newSubscribedEmail = new SubscribedEmails({
      newsletterEmail: req.body.newsletterEmail,
    });
    const SubscribedEmail = await newSubscribedEmail.save();

    const link = `${process.env.CLIENT_URL}/`;

    // 等待邮件发送完成
    await sendEmail(
      SubscribedEmail.newsletterEmail,
      "Welcome to our newsletter!",
      { link: link },
      `<div> <p>Hi,</p> <p>You are subscribed to our.</p> <p> Please click the link below to view more</p> <a href=${link}>GO</a> </div>`
    );

    // 仅发送一次响应,整合需要返回的信息
    return res.status(200).json({
      message: "Successfully subscribed",
      SubscribedEmail,
      link
    });
  } catch(err) {
    next(err);
  }
});

额外优化点

  • 移除路由中冗余的条件判断,简化逻辑
  • 确保sendEmail函数不再依赖外部的res对象,避免未定义错误

内容的提问来源于stack exchange,提问作者sultan.h

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:10:19