Erro envio email gmail por oauth2

Código

const nodemailer = require('nodemailer');
const { google } = require('googleapis');
const OAuth2 = google.auth.OAuth2;
const { request } = require('gaxios');

const clientSecret = 'ccccccccccccccccccmN';
const clientId =
  'xxxxxxxxxxxxxxxx.apps.googleusercontent.com';

const createTransporter = async () => {
  try {
    const oauth2Client = new OAuth2(
      clientId,
      clientSecret,
      'http://127.0.0.1:17002',
    );

    oauth2Client.setCredentials({
      refresh_token: 200,
    });

    const accessToken = await new Promise(async (resolve, reject) => {
      await oauth2Client.getAccessToken((err, token) => {
        if (err) {
          console.log('*ERR: ', err);
          reject();
        }
        resolve(token);
      });
    });

    const transporter = nodemailer.createTransport({
      service: 'gmail',
      auth: {
        type: 'OAuth2',
        user: 'guilherme@netsoft.eti.br',
        accessToken,
        clientId: clientId,
        clientSecret: clientSecret,
        refreshToken: 200,
      },
    });
    return transporter;
  } catch (err) {
    return err;
  }
};

export const sendMail = async () => {
  try {
    const mailOptions = {
      from: 'guilherme@netsoft.eti.br',
      to: 'guilherme@netsoft.eti.br',
      subject: 'Test',
      text: 'Hi, this is a test email',
    };

    let emailTransporter = await createTransporter();
    await emailTransporter.sendMail(mailOptions);
  } catch (err) {
    console.log('ERROR: ', err);
  }
};
*ERR:  GaxiosError: invalid_grant
    at Gaxios._request (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\node_modules\gaxios\src\gaxios.ts:146:15)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async OAuth2Client.refreshTokenNoCache (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\node_modules\google-auth-library\build\src\auth\oauth2client.js:212:19)
    at async OAuth2Client.refreshAccessTokenAsync (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\node_modules\google-auth-library\build\src\auth\oauth2client.js:247:19)
    at async OAuth2Client.getAccessTokenAsync (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\node_modules\google-auth-library\build\src\auth\oauth2client.js:276:23) {
  config: {
    retry: true,
    retryConfig: {
      httpMethodsToRetry: [Array],
      currentRetryAttempt: 0,
      retry: 3,
      noResponseRetries: 2,
      retryDelayMultiplier: 2,
      timeOfFirstRequest: 1747699865329,
      totalTimeout: 9007199254740991,
      maxRetryDelay: 9007199254740991,
      statusCodesToRetry: [Array]
    },
    method: 'POST',
    url: 'https://oauth2.googleapis.com/token',
    data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/9.15.1',
      'x-goog-api-client': 'gl-node/20.11.1'
    },
    paramsSerializer: [Function: paramsSerializer],
    body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    validateStatus: [Function: validateStatus],
    responseType: 'unknown',
    errorRedactor: [Function: defaultErrorRedactor]
  },
  response: {
    config: {
      retry: true,
      retryConfig: [Object],
      method: 'POST',
      url: 'https://oauth2.googleapis.com/token',
      data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      headers: [Object],
      paramsSerializer: [Function: paramsSerializer],
      body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      validateStatus: [Function: validateStatus],
      responseType: 'unknown',
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: { error: 'invalid_grant', error_description: 'Bad Request' },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=utf-8',
      date: 'Tue, 20 May 2025 00:11:05 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: 400,
    statusText: 'Bad Request',
    request: { responseURL: 'https://oauth2.googleapis.com/token' }
  },
  error: undefined,
  status: 400,
  [Symbol(gaxios-gaxios-error)]: '6.7.1'
}
ERROR:  TypeError: Cannot read properties of undefined (reading 'sendMail')
    at sendMail (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\src\e-mail-interno\email.ts:59:28)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at EmailInternoService.buscarTodos (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\src\e-mail-interno\e-mail-interno.service.ts:39:5)
    at EmailInternoController.buscarTodos (C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\src\e-mail-interno\e-mail-interno.controller.ts:35:12)
    at async C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\node_modules\@nestjs\core\router\router-execution-context.js:46:28
    at async C:\sistemas\workspace\retiro\backend\gh-retiro-crud-node\node_modules\@nestjs\core\router\router-proxy.js:9:17

O que pode ser este erro ?

Está dando erro ao tentar gerar o accessToken, como está utilizando o nodemailer você pode deixar que ele mesmo faz isso automaticamente. Só é necessário passar os parâmetros type, user, clientId, clientSecret e refreshToken.

const transporter = nodemailer.createTransport({
      service: 'gmail',
      auth: {
        type: 'OAuth2',
        user: 'guilherme@netsoft.eti.br',
        clientId: 'xxxxxxxxxxxxxxxx.apps.googleusercontent.com',
        clientSecret: 'ccccccccccccccccccmN',
        refreshToken: 'xxxx//yyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzz',
      },
    });

No caso do refreshToken você está atribuindo um valor numérico 200, mas ele tem que ser uma string que você obtém junto do clientId e clientSecret.

1 curtida

Obrigado @lucashpmelo

Mas como gero o refreshToken ?

Fiz o base64 do clientId e clientSecret para o refreshToken, mas não funcionou.

Você pode utilizar o OAuth 2.0 Playground para gerar o refreshToken.

Tem esse tutorial da Tecnospeed que da o passo a passo de como fazer.