Let's create a new express app, as usual:
npx express-generator --no-view
Install our normal dev tool, dotenv
:
npm install dotenv
Install nodemailer:
npm i nodemailer
Create a .env
file, create some variables GMAIL
and GMAIL_PASS
and leave it like that, we'll fill it out soon. Hook this up to app.js
with the standard require('dotenv').config();
.
Negative : Don't forget to create a .gitingore
file whenever you create a .env
file! You can use a template or just manually type in the files and folders you want to ignore. In our case, just ignore .env
and /node_modules
.
Then create a helpers/email.js
:
const nodemailer = require("nodemailer")
const emailHelpers={}
emailHelpers.sendTestEmail= async ()=>{
try{
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
service:"gmail",
auth: {
user: process.env.GMAIL,
pass: process.env.PASS,
}
});
// send mail with defined transport object
let info = await transporter.sendMail({
from: '"name" <example@gmail.com>', // sender address
to: "example1@gmail.com, example2@gmail.vn", // list of receivers
subject: "Hello ✔", // Subject line
text: "Hello world?", // plain text body
html: `<p>Hello, this is test email</p>`// html body
});
console.log("Message sent: %s", info.messageId);
} catch(err){
console.log(err)
}
module.exports = emailHelpers
If you are wondering how to come up with all of these codes, read nodemailer documentations).
Now in routes/index.js
, import the emailHelper, and then add:
const emailHelpers = require("../helpers/email.helper");
/* Temporary GET route to send myself an email. */
router.get("/test-email", (req, res) => {
emailHelpers.sendTestEmail();
res.send({ message: "email has been sent" });
});
One more step, we now need to set up your gmail, by taking the app password with gmail. If your account has two-factor authentication set up and you don't want to disable it, you'll need to create an "application-specific password". Generate an app password then put it in the .env
file along with your gmail. Read more here
Great! Now you can send a basic email. Trigger the email by either postman'ing or just opening a browser to localhost:5000/api/test-email
. (don't forget to put /api
here app.use('/api', indexRouter)
)
You should see an email in your inbox! If not, check your spam folder.
We need to create a template. For that, we will create new model to save our template
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const templateSchema = Schema(
{
name: { type: String, required: true },
description: { type: String, required: true },
template_key: { type: String, required: true, unique: true },
from: { type: String, required: true },
html: { type: String, required: true },
subject: { type: String, required: true },
variables: [{ type: String, required: true }],
},
{
timestamp: true,
}
);
const Template = mongoose.model("Template", templateSchema);
module.exports = Template;
const nodemailer = require("nodemailer");
const Template = require("../models/Template");
const emailHelper = {};
const emailInternalHelper = {};
emailInternalHelper.createTemplatesIfNotExists = async () => {
try {
let template = await Template.findOne({ template_key: "verify_email" });
if (!template) {
await Template.create({
name: "Verify Email Template",
template_key: "verify_email",
description: "This template is used when user register a new email",
from: "'CoderSchool Team' <social_blog@coderschool.vn>",
subject: `Hi %name%, welcome to CoderSchool!`,
html: `Hi <strong>%name%</strong> ,
<br /> <br />
Thank you for your registration.
<br /> <br />
Please confirm your email address by clicking on the link below.
<br /> <br />
%code%
<br /> <br />
If you face any difficulty during the sign-up, do get in
touch with our Support team: apply@coderschool.vn
<br /> <br /> Always be learning!
<br /> CoderSchool Team
`,
});
}
} catch (err) {
console.log(error);
}
};
module.exports = { emailInternalHelper, emailHelper };
createTemplatesIfNotExists
function when you connect to mongo DB app.jsconst mongoURI = process.env.MONGODB_URI;
mongoose
.connect(mongoURI, {
// some options to deal with deprecated warning
useCreateIndex: true,
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
})
.then(() => {
console.log(`Mongoose connected to ${mongoURI}`);
emailInternalHelper.createTemplatesIfNotExists();
})
.catch((err) => console.log(err));
User
schema. The two fields are:emailVerificationCode
- String type, that will be the "secret" code to verify a user.emailVerified
- Boolean type, that checks if the user has been validated or not. Optionally, you could also save this as a timestamp, and name it emailVerifiedAt
.Add these two fields, and now we'll create middleware around them.
emailHelper.renderEmailTemplate = async (
template_key,
variablesObj,
toEmail
) => {
const template = await Template.findOne({ template_key });
if (!template) {
return { error: "Invalid Template Key" };
}
const data = {
from: template.from,
to: toEmail,
subject: template.subject,
html: template.html,
};
for (let index = 0; index < template.variables.length; index++) {
let key = template.variables[index];
if (!variablesObj[key]) {
return {
error: `Invalid variable key: Missing ${template.variables[index]}`,
};
}
let re = new RegExp(`%${key}%`, "g");
data.subject = data.subject.replace(re, variablesObj[key]);
data.html = data.html.replace(re, variablesObj[key]);
}
return data;
};
emailHelpers.send = async (data) => {
try {
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.GMAIL,
pass: process.env.PASS,
},
});
let info = await transporter.sendMail(data);
console.log("Message sent: %s", info.messageId);
} catch (err) {
console.log(err);
}
};
module.exports = { emailHelper, emailInternalHelper };
const emailVerificationCode = utilsHelper.generateRandomHexString(20); // you need to create this helper
user = await User.create({
name,
email,
password,
avatarUrl,
emailVerificationCode,
emailVerified: false,
});
const verificationURL = `${FRONTEND_URL}/verify/${emailVerificationCode}`;
const emailData = await emailHelper.renderEmailTemplate(
"verify_email",
{ name, code: verificationURL },
email
);
if (!emailData.error) {
emailHelper.send(emailData);
} else {
throw new Error("Create email fail");
}
When you make email, you need to create email verify code. In order to make this code we will use some library call crypto
helpers/utils.js
npm install crypto
utilsHelper.generateRandomHexString = (len) => {
return crypto
.randomBytes(Math.ceil(len / 2))
.toString("hex") // convert to hexadecimal format
.slice(0, len)
.toUpperCase(); // return required number of characters
};
Now you can TEST!! Register new user! you will get the EMAIL
Now we need to use the code sent back the frontend. We need to:
true
.In users.controller.js
:
const verifyEmail = async (req, res, next) => {
const { code } = req.body.params;
let user = await User.findOne({
emailVerificationCode: code,
});
if (!user) {
res.status(400).json({ error: "Invalid Email Verification Token" });
return;
}
user = await User.findByIdAndUpdate(
user._id,
{
$set: { emailVerified: true },
$unset: { emailVerificationCode: 1 },
},
{ new: true }
);
res.json({ message: "Email verified!" });
};
Add the route to your users.js
routes:
router.get("/verify_email/:code", usersController.verifyEmail);
Test and see if it works.
Now that the server sends back a token, your frontend will automatically log the user in after they come and successfully verify their email. This will improve the user experience! To do this, go to your frontend project.
Figure out the flow of reset password then implement it with nodemailer