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的属性,所以循环自然不会执行。
修复步骤
修改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 }, // 包装成对象 };优化模板中的图片路径写法
模板里的{{this.0.image}}写法在Handlebars中可能有问题,建议改成标准的数组索引访问方式{{this.[0].image}},或者直接简化掉{{#with}}块:<td> <img class="item-image" src="{{this.product.images.[0].image}}" alt="Product Image" /> </td>额外验证点
- 确认
handlebarOptions中的viewPath和partialsDir确实指向了你的模板文件所在目录(./public/template),避免路径错误导致模板加载异常。 - 检查控制台是否有模板编译相关的错误信息,比如找不到变量的警告,这能帮你快速定位问题。
- 确认
验证修改
修改后重新触发订单流程,你应该能在收到的邮件中看到正确渲染的订单商品列表了。
备注:内容来源于stack exchange,提问作者code46
相关产品推荐
相关产品推荐

