I have an eCommerce solution and I want to send an email when an checkout is completed and an order is created. The problem is everything works and the template engine run the handlebars file. But when the mail is received, dynamic data passed to the file will not render.
This is my controller
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" });
});
So a data like this is sent to nodemailer
[
{
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")
}
]
This is nodemailer configuration to send the email
const checkoutMailer = asyncHandler(
async (data, assets, callback, _requestObject, _responseObject) => {
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: process.env.SENDMAIL_HOST_SERVER,
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: process.env.SENDMAIL_ACCOUNT_ID, // generated ethereal user
pass: process.env.SENDMAIL_AUTH_STRING, // generated ethereal password
},
});
const handlebarOptions = {
viewEngine: {
extName: ".handlebars",
partialsDir: path.resolve("./public/template"),
defaultLayout: false,
},
viewPath: path.resolve("./public/template"),
extName: ".handlebars",
};
transporter.use("compile", hbs(handlebarOptions));
// send mail with defined transport object
let mailOptions = {
from: '"Kelvin Kabute 👻" <skipgh@gmail.com>', // sender address
to: data.to, // list of receivers
subject: "Hello ✔ " + data.subject, // Subject line
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);
}
});
}
);
This is my handlebars file and this is where my issue is. From {{#each assets}} does not show in the email sent.
<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>
Please help me resolve this
First of all I have made some hefty mistakes.
let mailOptions = {
from: '"Kelvin Kabute 👻" <skipgh@gmail.com>', // sender address
to: data.to, // list of receivers
subject: "Hello ✔ " + data.subject, // Subject line
template: "order",
context: { assets },
};
ES6 object notation allow to use a single name to represent key/value
pair so the name I gave it is assets
2.assets
is not a valid json object since its data direct from mongoose and the data has not yet been converted to json. Inside the controller const assets = orderCheckedout.products
is replaced by
const assets = orderCheckedout.product.map((item) => {
return {
title: item.product.title,
brand: item.product.brand.name,
price: item.product.price,
image: item.product.images[0].image,
quantity: item.quantity,
};
});
Lastly and article I found online Send emails with Handlebars, nodemailer and Gmail Shared some light on how to compose my handlebars template. Each day we grow and sometimes we really make mistakes but we pick up and move on.