Getting 'unauthorized_client' error with Nodemailer and Google OAuth2 in Node.js

I have created a contact.js netlify function to handle mails which have to be sent to my gmail account. I am using nodemailer alongwith google api (oauth2.0) to send these mails, but netlify logs the following error : “errorType”:“Runtime.UnhandledPromiseRejection”,“errorMessage”:“Error: unauthorized_client”

This code was running fine on localhost, but now displas this error after deploying it. Note: All the credentials(env variables) are correct and properly stored. I created the client id, secret and refresh token by making a google cloud project and generating the Oauth credentials from there. And obtained the refresh token from the OauthPlayground2.0 website. This is the REDIRECT_URI: OAuth 2.0 Playground

Code [Contact.js Netlify function]

const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const dotenv = require("dotenv").config();

exports.handler = async function (event, context) {
  try {

    const { name, email, _subject, message } = JSON.parse(event.body);

    const oAuth2Client = new google.auth.OAuth2(
      process.env.CLIENT_ID,
      process.env.CLIENT_SECRET,
      process.env.REDIRECT_URI
    );

    oAuth2Client.setCredentials({
      refresh_token: process.env.REFRESH_TOKEN,
    });

    const transporter = nodemailer.createTransport({
      service: "gmail",
      auth: {
        type: "OAuth2",
        user: process.env.EMAIL_ADDRESS,
        clientId: process.env.CLIENT_ID,
        clientSecret: process.env.CLIENT_SECRET,
        refreshToken: process.env.REFRESH_TOKEN,
        accessToken: oAuth2Client.getAccessToken(),
      },
    });

    const mailOptions = {
      from: email,
      to: process.env.EMAIL_ADDRESS,
      subject: _subject,
      text: `Name: ${name}\nEmail: ${email}\nSubject: ${_subject}\nMessage: ${message}`,
    };

    await transporter.sendMail(mailOptions);

    return {
      statusCode: 200,
      body: JSON.stringify({ message: "Email sent successfully" }),
    };
  } catch (error) {
    console.error(error);

    return {
      statusCode: 500,
      body: JSON.stringify({ error: "Error sending email" }),
    };
  }
};

COMPLETE ERROR MESSAGE [AS PER FUNCTION LOG]
Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"Error: unauthorized_client","reason":{"errorType":"Error","errorMessage":"unauthorized_client","config":{"method":"POST","url":"https://oauth2.googleapis.com/token","data":"<<REDACTED> - See errorRedactoroption ingaxiosfor configuration>.","headers":{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"google-api-nodejs-client/9.4.1","x-goog-api-client":"gl-node/18.18.0"},"body":"<<REDACTED> - SeeerrorRedactoroption ingaxiosfor configuration>.","responseType":"unknown"},"response":{"config":{"method":"POST","url":"https://oauth2.googleapis.com/token","data":"<<REDACTED> - SeeerrorRedactoroption ingaxiosfor configuration>.","headers":{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"google-api-nodejs-client/9.4.1","x-goog-api-client":"gl-node/18.18.0"},"body":"<<REDACTED> - SeeerrorRedactoroption ingaxios for configuration>.","responseType":"unknown"},"data":{"error":"unauthorized_client","error_description":"Unauthorized"},"headers":{"alt-svc":"h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000","cache-control":"no-cache, no-store, max-age=0, must-revalidate","connection":"close","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Wed, 20 Dec 2023 09:14:55 GMT","expires":"Mon, 01 Jan 1990 00:00:00 GMT","pragma":"no-cache","server":"scaffolding on HTTPServer2","transfer-encoding":"chunked","vary":"Origin, X-Origin, Referer","x-content-type-options":"nosniff","x-frame-options":"SAMEORIGIN","x-xss-protection":"0"},"status":401,"statusText":"Unauthorized","request":{"responseURL":"https://oauth2.googleapis.com/token"}},"status":401,"stack":["Error: unauthorized_client"," at Gaxios._request (/var/task/node_modules/gaxios/build/src/gaxios.js:141:23)"," at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"," at async OAuth2Client.refreshTokenNoCache (/var/task/node_modules/google-auth-library/build/src/auth/oauth2client.js:175:19)"," at async OAuth2Client.refreshAccessTokenAsync (/var/task/node_modules/google-auth-library/build/src/auth/oauth2client.js:209:19)"," at async OAuth2Client.getAccessTokenAsync (/var/task/node_modules/google-auth-library/build/src/auth/oauth2client.js:238:23)"]},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: Error: unauthorized_client"," at process.<anonymous> (file:///var/runtime/index.mjs:1276:17)"," at process.emit (node:events:517:28)"," at emit (node:internal/process/promises:149:20)"," at processPromiseRejections (node:internal/process/promises:283:27)"," at process.processTicksAndRejections (node:internal/process/task_queues:96:32)"]}

Live Site URL

Not sure why you’d use OAuth for this instead of a personal access token. With that being said, this doesn’t seem like a Netlify issue. There’s something going wrong with your setup and we can’t guess what that could be.