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

Nodemailer + Express Handlebars发送动态邮件时迭代器无法渲染数据问题求助

Nodemailer + Express Handlebars发送动态邮件时迭代器无法渲染数据问题求助

问题描述

我开发了一个电商系统,想要在用户完成结账并创建订单时发送确认邮件。目前邮件能正常发送,Handlebars模板引擎也能加载,但收到的邮件里完全不显示传递的动态数据,模板里的{{#each assets}}循环没有渲染任何内容。

相关代码

1. 订单控制器代码

const getOrderCheckout = asyncHandler(async (requestObject, responseObject) => {

  const { id } = requestObject.params;

  const orderCheckedout = await Order.findOne({
    "paymentIntent.transactionId": id,
  })
  .populate({
    path: "product.product",
    populate: {
      path: "brand",
      model: "Brand",
    },
  })
  .populate("orderBy");

  if (orderCheckedout) {
    const data = {
      to: orderCheckedout.orderBy.email,
      subject: "Order sucessfully made",
    };
    const assets = orderCheckedout.product;
    checkoutMailer(data, assets, (status, message) => {
      responseObject.status(status).json({ message: message });
    });
    responseObject.status(200).json(orderCheckedout);
  } else
    responseObject
    .status(404)
    .json({ message: "Could not find any orders, please try again" });
});

2. 传递给Nodemailer的assets数据结构

[
  {
    product: {
      _id: new ObjectId("644d30d460b928dd60536b05"),
      title: 'Staedtler Triplus 0.3mm Pen',
      slug: 'office-supplies-staedtler-triplus-0.3mm-pen',
      price: 29,
      category: new ObjectId("6425ebbce7b7b9989c638f11"),
      brand: {name:"Staedtler"},
      quantity: 88,
      sold: 12,
      images: [{image:"<image path>")}],
      tags: [Array],
      totalRatings: '0',
      ratings: [],
      createdAt: 2023-04-29T14:59:32.318Z,
      updatedAt: 2023-06-10T12:51:31.736Z,
      __v: 1
    },
    quantity: 1,
    color: 'Blue',
    _id: new ObjectId("648471b1a1db266d6b1e8f51")
  }
]

3. Nodemailer配置代码

const checkoutMailer = asyncHandler(
  async (data, assets, callback, _requestObject, _responseObject) => {
    let transporter = nodemailer.createTransport({
      host: process.env.SENDMAIL_HOST_SERVER,
      port: 587,
      secure: false,
      auth: {
        user: process.env.SENDMAIL_ACCOUNT_ID,
        pass: process.env.SENDMAIL_AUTH_STRING,
      },
    });

    const handlebarOptions = {
      viewEngine: {
        extName: ".handlebars",
        partialsDir: path.resolve("./public/template"),
        defaultLayout: false,
      },
      viewPath: path.resolve("./public/template"),
      extName: ".handlebars",
    };

    transporter.use("compile", hbs(handlebarOptions));

    let mailOptions = {
      from: '"Kelvin Kabute " <skipgh@gmail.com>',
      to: data.to,
      subject: "Hello ✔ " + data.subject,
      template: "order",
      context: assets, // 这里是关键问题点
    };

    transporter.sendMail(mailOptions, function (error, info) {
      if (error) {
        console.log(error);
      } else {
        console.log("Email sent: " + info.response);
        let reqStatus, message;
        if (info.accepted) {
          reqStatus = 100;
          message = "Message sent successfully with order information, please check both spam and inbox";
        } else {
          reqStatus = 417;
          message = "System error, message not sent please contact us on 0800-2232-222";
        }
        callback(reqStatus, message);
      }
    });
  }
);

4. Handlebars模板代码

<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    .heading { font-size: 22px; color: #a2ce2c; } .banner { width: 300px; height: 80px; } .item-image { width: 90px; }
  </style>
</head>
<body>
  <div>
    <p class="heading">Order Items Being Processed</p>
    <img src="https://static.vecteezy.com/system/resources/thumbnails/005/021/047/small/illustration-land-or-ground-shipping-carrier-delivery-service-has-motorcycle-van-and-cargo-ship-yellow-or-orange-tones-design-for-banner-website-decorations-app-advertising-parcel-vector.jpg" alt="..." />
    {{#each assets}}
    <table>
      <tr>
        <th>Image</th>
        <th>Brand</th>
        <th>Item</th>
        <th>Quantity</th>
        <th>Unit Price</th>
      </tr>
      <tr>
        <td>
          {{#with this.product.images}}
          <img class="item-image" src={{this.0.image}} alt="..." />
          {{/with}}
        </td>
        <td>{{this.product.brand.name}}</td>
        <td>{{this.product.title}}</td>
        <td>{{this.quantity}}</td>
        <td>{{this.product.price}}</td>
      </tr>
    </table>
    {{/each}}
  </div>
</body>
</html>

收到的邮件效果

邮件中只显示静态的标题和图片,{{#each assets}}循环部分完全没有渲染,看不到任何订单商品信息。


问题分析与解决方案

核心问题:上下文传递不匹配

你在mailOptions里直接把assets数组作为context传递给Handlebars,但模板里却试图遍历assets这个属性——这时候Handlebars的根上下文就是这个数组本身,根本不存在名为assets的属性,所以循环自然不会执行。

修复步骤

  1. 修改Nodemailer的context传递方式
    把assets数组包装成一个包含assets属性的对象,这样模板里的{{#each assets}}就能正确找到对应的数据:

    let mailOptions = {
      from: '"Kelvin Kabute " <skipgh@gmail.com>',
      to: data.to,
      subject: "Hello ✔ " + data.subject,
      template: "order",
      context: { assets: assets }, // 包装成对象
    };
    
  2. 优化模板中的图片路径写法
    模板里的{{this.0.image}}写法在Handlebars中可能有问题,建议改成标准的数组索引访问方式{{this.[0].image}},或者直接简化掉{{#with}}块:

    <td>
      <img class="item-image" src="{{this.product.images.[0].image}}" alt="Product Image" />
    </td>
    
  3. 额外验证点

    • 确认handlebarOptions中的viewPath和partialsDir确实指向了你的模板文件所在目录(./public/template),避免路径错误导致模板加载异常。
    • 检查控制台是否有模板编译相关的错误信息,比如找不到变量的警告,这能帮你快速定位问题。

验证修改

修改后重新触发订单流程,你应该能在收到的邮件中看到正确渲染的订单商品列表了。

备注:内容来源于stack exchange,提问作者code46

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 09:29:38