Lab3: Sending email in Express with Nodemailer
Lab3: Sending email in Express with Nodemailer
[]: # (c) 2023 Kenny Cheng []: # License: CC-BY-SA []: # Version: 1.0 []: # Status: draft []: # Tags: node.js, email, smtp, nodemailer []: # Description: This lab shows how to send emails with Node.js.
Introduction
Sending emails is a common task in web applications. In this lab, we will use the Nodemailer library to send emails from a Node.js application. Nodemailer is a library that allows you to send emails from a Node.js application with easy to use APIs. It supports SMTP, sendmail transports Azure, Amazon SES, Mailgun, SparkPost, and many more. It also supports OAuth2 authentication. In this lab, we will use the SMTP transport to send emails.
Prerequisites
In order to complete this lab, you need to have the following installed on your computer:
Let’s get started by installing the Nodemailer library in your current project. Please open your project in VSCode and Open a terminal and type the following command:
npm install --save nodemailer
It will install the latest version of Nodemailer and add it to your project’s dependencies (package.json).
Sending emails in Express
Let’s start by creating a new route in our Express application. Open the routes/index.js
file and add the following code:
const nodemailer = require("nodemailer");
router.get('/api/sendmail', async function(req, res, next) {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
let testAccount = await nodemailer.createTestAccount();
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: testAccount.user, // generated ethereal user
pass: testAccount.pass, // generated ethereal password
},
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: '"Fred Foo 👻" <foo@example.com>', // sender address
to: "bar@example.com, baz@example.com", // list of receivers
subject: "Hello ✔", // Subject line
text: "Hello world?", // plain text body
html: "<b>Hello world?</b>", // html body
});
return res.json({message: 'mail sent', info: info, previewUrl: nodemailer.getTestMessageUrl(info)});
});
Implementing email templates with ejs
Adding ejs as a dependency
To use ejs as a template engine in express, we need to add it as a dependency to our project. Open a terminal and type the following command:
npm install --save ejs
Adding ejs as a view engine
Please modify the app.js
file and add the following code after var app = express();
:
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
Creating an email template
Please add a new folder called views
in the root of your project. Then create a new file called email.ejs
and add the following code:
<!DOCTYPE html>
<html>
<head>
<title>My Email</title>
</head>
<body>
<h1>My Email</h1>
<p>Hi <%= name %>,</p>
<p>This is my email.</p>
</body>
</html>
Rendering the email template as string in route handler
Let’s modify the original route handler to render the email template as a string for the email body. Please modify the routes/index.js
file and change the code of await transporter.sendMail({ /*...*/ })
to following code:
// render the email template as a string, getting it back in the callback as "html"
req.app.render('email', { name: 'Tobi' }, async function (err, html) {
// send mail with defined transport object
let info = await transporter.sendMail({
from: '"Fred Foo 👻" <foo@example.com>', // sender address
to: "bar@example.com, baz@example.com", // list of receivers
subject: "Hello ✔", // Subject line
html: html, // html body
attachments: [
{ // file on disk as an attachment from the root of project folder
filename: 'package.json',
path: 'package.json' // stream this file
},
{ // use URL as an attachment
filename: 'license.txt',
path: 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'
}
]
});
res.json({message: 'mail sent', info: info, previewUrl: nodemailer.getTestMessageUrl(info)});
})
req.app.render(...)
is a function provided by Express to render a template as a string. It takes the name of the template file, the data to be passed to the template, and a callback function as parameters. The callback function will be called when the template is rendered as a string. The rendered string will be passed to the callback function as the second parameter. However, it dose not provide an async/await version of the function. So we need to use the callback function to send the email. If that is the case, we can use Promise to wrap the req.app.render(...)
function. Please modify the code to the following code:
let getRenderedHTML = new Promise((resolve, reject) => {
req.app.render('email', { name: 'Tobi' }, function (err, html) {
if (err) {
reject(err);
} else {
resolve(html);
}
});
});
let info = await transporter.sendMail({
from: '"Fred Foo 👻" <foo@example.com>', // sender address
to: "bar@example.com, baz@example.com", // list of receivers
subject: "Hello ✔", // Subject line
html: await getRenderedHTML, // html body
attachments: [
{ // file on disk as an attachment from the root of project folder
filename: 'package.json',
path: 'package.json' // stream this file
},
{ // use URL as an attachment
filename: 'license.txt',
path: 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'
}
]
});
return res.json({message: 'mail sent', info: info, previewUrl: nodemailer.getTestMessageUrl(info)});
If we really want to have function call style in await expression, we can wrap the Promise with a function. Please modify the code to the following code:
// render the email template as a string, getting it back in the callback as "html"
let getRenderedHTML = () => new Promise((resolve, reject) => {
req.app.render('email', { name: 'Tobi' }, function (err, html) {
if (err) {
reject(err);
} else {
resolve(html);
}
});
});
let info = await transporter.sendMail({
from: '"Fred Foo 👻" <foo@example.com>', // sender address
to: "bar@example.com, baz@example.com", // list of receivers
subject: "Hello ✔", // Subject line
html: await getRenderedHTML(), // html body
attachments: [
{ // file on disk as an attachment from the root of project folder
filename: 'package.json',
path: 'package.json' // stream this file
},
{ // use URL as an attachment
filename: 'license.txt',
path: 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'
}
]
});