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

部署于Cyclic的NodeJS应用无法通过Brevo发送事务邮件求助

NodeJS部署Cyclic后Brevo事务邮件无法发送(本地正常)

问题描述

使用Brevo(原Sendinblue)发送验证、密码重置类事务邮件,NodeJS应用部署到Cyclic平台后,其他功能正常,但邮件完全无法发送。本地测试时邮件发送一切正常,部署后接口返回200状态码,Cyclic日志无报错,但收件方始终收不到邮件,怀疑问题出在Brevo配置或部署环境适配。

相关代码

sendEmail.js

const nodemailer = require("nodemailer");

const sendEmail = async (email, subject, text) => {
  try {
    const transporter = nodemailer.createTransport({
      service: "SendinBlue",
      auth: {
        user: process.env.SIB_EMAIL,
        pass: process.env.SIB_PASS,
      },
    });

    const mailOptions = {
      from: process.env.SIB_EMAIL,
      to: email,
      subject: subject,
      html: text,
    };

    transporter.sendMail(mailOptions, (error, info) => {
      if (error) {
        console.log("Error:", error);
      } else {
        console.log("Email sent:", info.response);
      }
    });
  } catch (err) {
    console.log(err);
  }
};

module.exports = sendEmail;

/forgot-password 接口

const forgotPassword = async (req, res) => {
  try {
    const { email } = req.body;
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(404).json({ success: true, message: "User not found" });
    }
    const resetToken = uuid();
    user.reset_token = resetToken;
    user.reset_token_expiry = Date.now() + 3600000; //1 hour

    await user.save();

    const resetLink = `${process.env.FRONTEND_URI}/reset-password?resetToken=${resetToken}`;

    const emailContent = `
    <p>Click on the link below to reset your password</p>
    <a href=${resetLink}>${resetLink}</a>
    <br>
    <h5>Team Sukoon</h5>
    `;

    sendEmail(email, "Sukoon: Reset your password", emailContent);
    res.status(200).json({ success: true, message: "Email sent successfully" });
  } catch {
    res.status(500).json({ success: false, message: "server error" });
  }
};

排查与修复方案

1. 检查Brevo IP白名单限制

Brevo默认可能拦截陌生IP的API请求,Cyclic服务器IP不在白名单内会导致请求被静默拦截(无错误返回但邮件不发送):

  • 登录Brevo后台,进入「设置」→「SMTP & API」→「API密钥」页面,查看是否开启IP白名单
  • 若开启,需将Cyclic平台的出站IP添加至白名单(可在Cyclic控制台环境信息中获取IP段)
  • 可临时关闭IP白名单做测试,确认是否为该问题

2. 修正Nodemailer的Brevo SMTP配置

当前使用的service: "SendinBlue"为旧兼容配置,Brevo更新后官方推荐直接指定SMTP参数,且SMTP认证密码需使用Brevo API密钥,而非账户登录密码:
修改sendEmail.js的transporter配置:

const transporter = nodemailer.createTransport({
  host: 'smtp-relay.brevo.com', // Brevo官方SMTP地址
  port: 587, // 非加密端口,465为SSL端口
  secure: false, // 587端口设为false,465端口设为true
  auth: {
    user: process.env.SIB_EMAIL,
    pass: process.env.SIB_API_KEY, // 替换为Brevo生成的API密钥
  },
});

3. 修复异步调用的错误捕获逻辑

原代码中transporter.sendMail使用回调模式,无法被外层async函数的try/catch捕获,导致错误无法在Cyclic日志中打印:
修改sendEmail.js为Promise风格,确保错误可被捕获:

const sendEmail = async (email, subject, text) => {
  try {
    const transporter = nodemailer.createTransport({
      host: 'smtp-relay.brevo.com',
      port: 587,
      secure: false,
      auth: {
        user: process.env.SIB_EMAIL,
        pass: process.env.SIB_API_KEY,
      },
    });

    const mailOptions = {
      from: process.env.SIB_EMAIL,
      to: email,
      subject: subject,
      html: text,
    };

    // 改用await Promise形式,让错误进入try/catch
    const info = await transporter.sendMail(mailOptions);
    console.log("Email sent:", info.response);
    return info;
  } catch (err) {
    console.error("Email send error:", err);
    throw err; // 抛出错误让上层接口处理
  }
};

同时在/forgot-password接口中添加await,确保发送失败时能进入catch块:

// ... 其他代码不变
await sendEmail(email, "Sukoon: Reset your password", emailContent);
res.status(200).json({ success: true, message: "Email sent successfully" });
// ...

4. 验证Cyclic环境变量

确认Cyclic平台上的SIB_EMAIL、SIB_API_KEY环境变量是否正确设置,无拼写错误或值粘贴错误。

5. 查看Brevo邮件日志

登录Brevo后台,进入「交易邮件」→「日志」页面,查看是否有被拒绝、静默丢弃的请求,这里能看到Brevo端的具体处理结果(如认证失败、IP拦截等)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 10:32:45