What You'll Learn

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

  1. Create Model : models/emailTemplate.js
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;
  1. Make the email helper function to create the new template data helpers/email.js
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 };
  1. Call this createTemplatesIfNotExists function when you connect to mongo DB app.js
const 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));
  1. To support email verification, we'll have to add a few fields to our User schema. The two fields are:

Add these two fields, and now we'll create middleware around them.

  1. Create the function to send email ** helpers/email.helper.js **
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 };
  1. Send email when user register controllers/userController.js
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

  1. Create helpers/utils.js
  2. npm install crypto
  3. Make function to generate random hex string.
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:

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