DEV Community

Cover image for Practical Guide to Send Emails from NodeJS/Express App using Gmail and Nodemailer (Screenshots and Code)
Stephen Omoregie
Stephen Omoregie

Posted on

Practical Guide to Send Emails from NodeJS/Express App using Gmail and Nodemailer (Screenshots and Code)

Errrm....so no long story on today's article. Leggo!

What You'll need.

  1. An App Password from your Google Account
  2. A NodeJS/Express App Set Up (Do I need to say this? haha).
  3. Packages: Nodemailer, Handlebars

Why bother? Well, you get access to free 500 sending limits per day. You can choose to upgrade to the Google Workspace account for about 2000 per day as your application grows.

Creating an App Password from Your Google Account.

Open your Google account settings (Manage Google Account), and select the security tab. Ensure you have 2-Step-Verification active, as you'll need this to be able to create an app password.

Check 2-Step Verification

Scroll down to the screen and select App Passwords. Note: I had a weird experience not seeing that option when I was working on mine, so you can visit https://myaccount.google.com/apppasswords to open the App Password page.
App Password Screen

If you have previously created any app Password (Note: App password is not related to your gmail/google account password - you can think of it as a token that allows you sign up from other applications e.g your nodejs server).

Enter a name (anything works, you won't use it elsewhere), then click on Create. This will open a modal with your 16-digit code - copy it and store in your .env file.

Create App Password Modal

That's all.

Example .env File for configuration

GOOGLE_APP_PASSWORD="your-app-password-here"
GOOGLE_USER="your-email-address@gmail.com"
GOOGLE_SENDER_MAIL="your-email-address@gmail.com"
Enter fullscreen mode Exit fullscreen mode

Install the Required Dependencies

npm install nodemailer handlebars 
Enter fullscreen mode Exit fullscreen mode

If you're using typescript, you should also install the types for the libraries

npm install @types/nodemailer  
Enter fullscreen mode Exit fullscreen mode

Quick Gist about Handlebars

It's a package that allows you create dynamic html files by providing placeholders for values that will be replaced when you compile it. Below is a simple example of a handlebar template for sending an OTP Code to a user.

<!--project_root/html/OTPEmail.hbs-->

<html lang='en'>
  <head>
    <meta charset='UTF-8' />
    <meta name='viewport' content='width=device-width, initial-scale=1.0' />
    <title>Your Title</title>
    <style>
      body { font-family: Arial, sans-serif; line-height: 1.6; color: #333;
      max-width: 600px; margin: 0 auto; padding: 20px; } .container {
      background-color: #f9f9f9; border-radius: 5px; padding: 20px; margin-top:
      20px; } .logo { text-align: center; margin-bottom: 20px; } .logo img {
      max-width: 150px; } h1 { color: #33CC66; } .otp { font-size: 24px;
      font-weight: bold; text-align: center; margin: 20px 0; padding: 20px;
      background-color: #33CC66; color: "white"; border-radius: 10px; } .footer
      { margin-top: 20px; text-align: center; font-size: 12px; color: #888; }
    </style>
  </head>
  <body>
    <div class='container'>
      <div class='logo'>
         <img src='https://yourwebsite.com/logo.png' alt='Joobz Logo' />
      </div>
      <h1>Welcome to Your_App!</h1>

      <!-- NOTE: USE DOUBLE CURLY BRACES TO WRAP THE VARIABLE. Dev.to is not letting me do so and remove this comment -->
      <p>Hello {username},</p>
      <p>To complete your registration,
        please use the following One-Time Password (OTP) to verify your email
        address:</p>

     <!-- NOTE: USE DOUBLE CURLY BRACES TO WRAP THE VARIABLE. Dev.to is not letting me do so  and remove this comment-->
      <div class='otp'>{otpCode}</div>
      <p>This OTP is valid for the next 5 minutes. If you didn't request this
        verification, please ignore this email.</p>
      <p>If you have any questions or need assistance, please don't hesitate to
        contact our support team.</p>
      <p>Best regards,<br />The Joobz Team</p>
    </div>
    <div class='footer'>
      <p>This is an automated message, please do not reply to this email.</p>
      <p>&copy; 2024 Your App. All rights reserved.</p>
    </div>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

The dynamic values are wrapped inside double curly braces - username and otpCode. They will be replaced when the code runs.

Setting Up Reusable Functions For Loading the Template, Configuring Nodemailer and Sending Mails.

You'll likely be sending more than one type of email to your users, and you'll also likely have many handlebar templates to use within your html directory.

So the codes below will help us handle that scenario. We'll be creating everything inside the utils/handleSendEmail.ts file in your project root so we can export it as the default and use anywhere. (You can adjust based on your project structure.

Import your environment variables and other required packages

import * as Handlebars from 'handlebars';
import { readFileSync } from 'fs';
import * as path from 'path';
import { GOOGLE_APP_PASSWORD, GOOGLE_SENDER_MAIL, GOOGLE_USER } from '../config';
import * as nodemailer from 'nodemailer';
Enter fullscreen mode Exit fullscreen mode

First, let's define the main function that will call the other utility functions

Set up a function that will load your html template and convert it to html string

/**
 * main - Default export function 
 * @param template - Provide the name of the template stored in the html directory. Do not include the .hbs extension
 * @param recipient - Email Address of recipient.
 * @param subject - Subject of the Email
 * @param data - Data that will be compiled into your handlebars template (based on the placeholders).
 * @returns - HTML String or Empty string on error.
 */
async function main(template: string, recipient: string, subject: string, data: any) {
  const html = await readHtmlTemplate(template, data);

  if (html === '') {
    console.log('UNABLE TO READ TEMPLATE FILE');
    return;
  }
  sendEmailMessage(recipient, subject, html);
}

export default main;
Enter fullscreen mode Exit fullscreen mode

Utility function that will load your html template and convert it to html string

const readHtmlTemplate = async (templatename: string, data: any) => {
  try {
    // Adjust based on your project file structure
    const templatePath = path.join(__dirname, '..',
      'html', `${templatename}.hbs`);
    const htmlSource = readFileSync(templatePath, 'utf-8');
    const template = Handlebars.compile(htmlSource);
    const html = template(data);

    return html;
  } catch (error) {
    console.log('ERROR READING TEMPLATE: ', error);
    return '';
  }
};
Enter fullscreen mode Exit fullscreen mode

Utility Function that sets up the transporter object and sends the mail using the parameters passed in the main driver function (First function above).

const sendEmailMessage = async (recipient: string, subject: string, html: string) => {
  // Define transporter
const transporter = nodemailer.createTransport({
  service: 'gmail',
  secure: false, //To use TLS
  auth: { user: GOOGLE_USER, pass: GOOGLE_APP_PASSWORD,},
});

  // Define Mail Options
  const mailOptions = {
    from: GOOGLE_SENDER_MAIL,
    to: recipient,
    subject: subject,
    html: html,
  };

  //   Send Mail with the transporter
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('Error sending email: ', error);
    } else {
      console.log('Email sent: ', info.response);
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

And that's it. Remember to export the main function as teh default export for the module.

export default main;
Enter fullscreen mode Exit fullscreen mode

Now, to use the function, all you have to do is, import it wherever you need it, and pass the required parameters.
Example:


import { Request, Response } from 'express';
import { User } from '../models/User'; 
import { generateOTP } from '../utils/otpGenerator';
import handleSendEmail from '../utils/emailService'; 

export const registerUser = async (req: Request, res: Response) => {
  try {
    const { fullName, email, password } = req.body;

    // Check if user already exists
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      return res.status(400).json({ message: 'User already exists' });
    }
    // Create new user
    // ofcourse you'll hash your password first
    const newUser = new User({fullName, email, password});

    // Generate OTP
    const randomOTP = generateOTP();
    newUser.otp = randomOTP;
    newUser.otpExpires = new Date(Date.now() + 5 * 60 * 1000); // OTP expires in 5 minutes

    // Save user
    await newUser.save();

    // Send OTP to user via Email
    await handleSendEmail('OTPEmail', email, 'Verify OTP ✅', {
      username: fullName.split(' ')[0],
      otpCode: randomOTP,
    });

    res.status(201).json({ 
      message: 'User registered successfully. Please check your email for OTP verification.',
      userId: newUser._id
    });
  } catch (error) {
    console.error('Registration error:', error);
    res.status(500).json({ message: 'Error registering user' });
  }
};
Enter fullscreen mode Exit fullscreen mode

Code in Action (As Used in my project)

Code In Action

Conclusion

Phew, that's a lot. I Hope you find the guide useful, simple and straight-forward in setting up Gmail and Node Mailer for your project.

Happy Coding guys, and till the next article. Keep learning.
Let's connect on twitter: https://x.com/Cre8steveDev

Cre8steveDev

Top comments (0)