DEV Community

Pratik Kumar Ghosh
Pratik Kumar Ghosh

Posted on

How I Set Up Nodemailer with Gmail OAuth2 in My Node.js Project

For a long time, I thought sending emails from a Node.js project would be simple.
Install Nodemailer, write a few lines, done.

Not really.

While adding email sending functionality to my project, I ran into configuration issues, especially with dotenv, Gmail authentication, and OAuth2. After some debugging, I finally got it working.

So in this post, I want to explain how to set up Nodemailer with Gmail OAuth2 in a simple way, and also share the exact problems I faced and how I fixed them.


Why I used OAuth2 instead of password login

Earlier, many people used Gmail with just email and password. But that is not the recommended way now.

A better setup is:

  • Gmail API enabled
  • OAuth2 credentials from Google Cloud
  • Refresh token from OAuth Playground
  • Nodemailer configured with those values

That is also the method used in the guide I followed.


Step 1: Install packages

npm install nodemailer dotenv
Enter fullscreen mode Exit fullscreen mode

Step 2: Create OAuth2 credentials

Go to Google Cloud Console and do these things:

  • Create a project
  • Enable Gmail API
  • Create OAuth 2.0 Client ID
  • Choose Web Application
  • Add redirect URI:
    • http://localhost
    • https://developers.google.com/oauthplayground

After that, you will get:

  • CLIENT_ID
  • CLIENT_SECRET

Step 3: Generate refresh token

Open OAuth 2.0 Playground and:

  • Click the settings icon
  • Enable Use your own OAuth credentials
  • Paste your CLIENT_ID and CLIENT_SECRET
  • Set access type to Offline
  • Select the Gmail scope:
https://mail.google.com/
Enter fullscreen mode Exit fullscreen mode
  • Authorize APIs
  • Exchange authorization code for tokens

Now copy the refresh token.


Step 4: Create your .env file

GOOGLE_USER=yourgmail@gmail.com
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REFRESH_TOKEN=your_refresh_token
Enter fullscreen mode Exit fullscreen mode

Make sure the variable names match exactly with your code.


Step 5: Configure Nodemailer

Here is a clean setup:

import dotenv from "dotenv";
import nodemailer from "nodemailer";

dotenv.config();

const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    type: "OAuth2",
    user: process.env.GOOGLE_USER,
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    refreshToken: process.env.GOOGLE_REFRESH_TOKEN,
  },
});

// Verify the connection configuration
transporter.verify((error, success) => {
  if (error) {
    throw new Error(`Error connecting to email server: ${error.message}`);
  } else {
    console.log("Email server is ready to send messages");
  }
});

// Function to send email
export const sendEmail = async ({ to, subject, text, html }) => {
  try {
    const info = await transporter.sendMail({
      from: `"Your Name" <${process.env.Gmail_User}>`, // sender address
      to, // list of receivers
      subject, // Subject line
      text, // plain text body
      html, // html body
    });

    console.log('Message sent: %s', info.messageId);
    console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
  } catch (error) {
    throw new Error(`Error sending email: ${error.message}`);
  }
};
Enter fullscreen mode Exit fullscreen mode

Step 6: Use it in your project

await sendEmail({
  to: "recipient@example.com",
  subject: "Test Mail",
  text: "This is a test email",
  html: "<p>This is a test email</p>",
});
Enter fullscreen mode Exit fullscreen mode

If everything is correct, your mail should be sent successfully.


What I learned

Setting up Nodemailer is not just about writing sendMail().

You also need to understand:

  • how Gmail authentication works
  • why OAuth2 is needed
  • why environment variables must load properly
  • why even small config mistakes can break everything

This was one of those features that looked small from outside, but taught me a lot while implementing it.


Problems I faced and how I solved them

1. My dotenv configuration was done afterwards

This was my main mistake.

I had set up the email functionality, but the environment variables were not being loaded properly at the right time. Because of that, values like client ID, client secret, refresh token, or email user were not available when Nodemailer needed them.

Fix

I made sure to call:

dotenv.config();
Enter fullscreen mode Exit fullscreen mode

at the top, before using process.env values.

That small order issue was enough to break the whole setup.


2. Gmail authentication error

I got an error like:

530-5.7.0 Authentication Required
Enter fullscreen mode Exit fullscreen mode

This happened because Gmail was not accepting the request as properly authenticated.

Fix

I stopped treating it like a simple email-password login and used the correct OAuth2 setup:

  • Gmail API enabled
  • correct client ID
  • correct client secret
  • correct refresh token
  • correct Gmail account in GOOGLE_USER
  • add same Gmail account as a test user in Google Cloud Console

3. Following the guide was not enough without matching my own project structure

The guide I followed was helpful, but I still had to adjust it to my own project setup.

For example:

  • I was using my own environment variable names
  • my project used import syntax
  • my file structure was different from the guide

Fix

I kept the logic the same, but adapted the code to my own project.

That made me realize an important thing:

Tutorials and README files give the path, but you still need to fit that path into your own project properly.


4. Small config mistakes can waste a lot of time

Even when the code looks correct, things can still fail because of:

  • wrong variable names
  • wrong refresh token
  • wrong Google account
  • missing .env
  • dotenv loading too late

Fix

I checked everything one by one instead of changing random things.

That made debugging much easier.


Final thoughts

This feature looked small, but it taught me a very useful lesson:

Backend development is not only about writing logic. A lot of real work is in configuration, authentication, environment setup, and debugging.

Getting Nodemailer working with Gmail OAuth2 was frustrating at first, but once it worked, I understood the setup much better.

And honestly, that is the good part of building projects:
you do not just learn the feature, you learn the mistakes around the feature too.

Top comments (0)