NodeMailer - send mail with Google service account fails because "Username and Password not accepted"

Viewed 6001

I'm creating a Twitter bot and I'm implementing a method that sends me a email if there is an error. As I'm already using the google API to access Google Drive (have no problem here), I decided to use the service account to send the email (Google console says it could be used that way)

The method I've come up to send the email so far is:

var config = require('./config/mail');
var google = require('./config/google');
var nodemailer = require('nodemailer');

var send = function (args) {
  let transporter = nodemailer.createTransport({
    'service': 'gmail',
    'auth': {
        'type': 'OAuth2',
        'user': google.client_email,
        'serviceClient': google.client_id,
        'privateKey': google.private_key
    }
  });
  transporter.on('token', token => console.log(token));

  let message = {
    'from': `"${config.serverFromName}" <${config.serverFromMail}>`,
    'to': args.to,
    'subject': args.subject,
    'text': args.text,
    'html': `<p>${args.text}</p>`
  };

  transporter.sendMail(message, (err, info) => {
    if (err) {
      console.log('Mail couldn\'t be sent because: ' + err);
    } else {
      console.log('Mail sent');
    }
  });
};

The config/google file contains the data that Google generates for you when you create a service account. config.serverFromName and config.serverFromMail are the name and email of the sender (not the same as the service account id). args contains the recipent email and the content

When I test the send method, I got the following message in my console:

Mail couldn't be sent because: Error: Invalid login: 535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8  https://support.google.com/mail/?p=BadCredentials z123sm543690vkd.10 - gsmtp

I know the token is being created correctly because the listener I created is printing it:

{ user: 'name@project.iam.gserviceaccount.com',
  accessToken: 'ya29.ElmIBLxzfU_kkuZeyISeuRBeljmAe7HNTlwuG4K12ysUNo46s-eJ8NkMYHQqD_JrqTlH3yheNc2Aopu9B5vw-ivEqvPR4sTDpWBOg3xUU_4XiJEBLno8FHsg',
  expires: 1500151434603 }

Searching on the Internet I found that it may be a problem with the OAuth scope. However, all the info that talks about it refers to using Client IDs, not service accounts. I don't find that option in the Google developer console, either.

Any ideas of what I'm doing wrong?

3 Answers

After visiting the OAuth 2.0 Playground and experimenting with all possible variations of gmail-related sub-scopes, even selecting them altogether...

https://www.googleapis.com/auth/gmail.labels
https://www.googleapis.com/auth/gmail.send
https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.compose
https://www.googleapis.com/auth/gmail.insert
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/gmail.metadata
https://www.googleapis.com/auth/gmail.settings.basic
https://www.googleapis.com/auth/gmail.settings.sharing

...the error message described in the OP title still persist:

Error: Invalid login: 535-5.7.8 Username and Password not accepted

It seems that NodeMailer is not capable of connecting via the scopes mentioned above. In fact, it explicitly mentions in the "Troubleshooting" section of its OAuth2 SMTP transport docs

The correct OAuth2 scope for Gmail SMTP is https://mail.google.com/, make sure your client has this scope set when requesting permissions for an user

Although this gives access to more than just sending emails, it works!

The only alternative to reach a more fine grained scope solution seems to be to resort to google's own Gmail API, where you can pass scopes when generating the OAuth2 client (which should of course at least include the scopes granted at the time the OAuth consent screen was shown):

oAuth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: SCOPES,
})

I was able to get service accounts working with Google & nodemailer:

these were the steps:

  1. Log in to console.- https://console.cloud.google.com/
  2. Create a service account under the project.
  3. Click on the new service account, go to permissions and add a member. You will use this member's email address when sending the request.
  4. Create keys for the service account. - keys -> add key. https://console.cloud.google.com/iam-admin/serviceaccounts
  5. Download your key file. You will get something like service-account-name-accountid.json. It will have all the information you need to get the code below running.
  6. Delegate authority to your service account https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority. Addhttps://mail.google.com/ as the scope.
  7. Write some code like below:

const nodemailer = require('nodemailer');
const json = require('./service-account-name-accountid.json');

const sendEmail = async (email, subject, text) => {
    try {

        const transporter = nodemailer.createTransport({
            host: 'smtp.gmail.com',
            port: 465,
            secure: true,
            auth: {
                type: 'OAuth2',
                user: email, //your permissioned service account member e-mail address
                serviceClient: json.client_id,
                privateKey: json.private_key
            }
        });

        await transporter.verify();
        
        await transporter.sendMail({
                from: json.service_email,
                to: email, //you can change this to any other e-mail address and it should work!
                subject,
                text
        });
        console.log('success!');
        return {
            status : 200
        }

    } catch (error) {
        console.log(error);
        return {
            status : 500,
            error
        }
    }
}

sendEmail('your_permissioned_service_account_email_address@some_place.com, 'testing 123', 'woohoo!');
Related