Errrm....so no long story on today's article. Leggo!
What You'll need.
- An App Password from your Google Account
- A NodeJS/Express App Set Up (Do I need to say this? haha).
- 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.
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.
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.
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"
Install the Required Dependencies
npm install nodemailer handlebars
If you're using typescript, you should also install the types for the libraries
npm install @types/nodemailer
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>© 2024 Your App. All rights reserved.</p>
</div>
</body>
</html>
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';
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;
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 '';
}
};
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);
}
});
};
And that's it. Remember to export the main function as teh default export for the module.
export default main;
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' });
}
};
Code in Action (As Used in my project)
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