google-api-nodejs-client: Failed sending mail with service account and JWT auth

I’m trying to send an email using a service account and JWT authentication and keep getting and error with a very unhelpful message: { code: 500, message: null }

This code snippet is from the following StackOverflow link: http://stackoverflow.com/questions/25207217/failed-sending-mail-through-google-api-in-nodejs

It seems like the solution there was to change the key in the parameters to resource instead of message but it’s not working for me. This is strange because in the JS example in the docs (https://developers.google.com/gmail/api/v1/reference/users/messages/send) it claims the key is still message

I’m authenticating with

var jwtClient = new google.auth.JWT(config.SERVICE_EMAIL, config.SERVICE_KEY_PATH, null, config.ALLOWED_SCOPES);

then sending an email with

jwtClient.authorize(function(err, res) {
  if (err) return console.log('err', err);

  var email_lines = [];

  email_lines.push("From: \"Some Name Here\" <rootyadaim@gmail.com>");
  email_lines.push("To: hanochg@gmail.com");
  email_lines.push('Content-type: text/html;charset=iso-8859-1');
  email_lines.push('MIME-Version: 1.0');
  email_lines.push("Subject: New future subject here");
  email_lines.push("");
  email_lines.push("And the body text goes here");
  email_lines.push("<b>And the bold text goes here</b>");

  var email = email_lines.join("\r\n").trim();

  var base64EncodedEmailSafe = new Buffer(email).toString('base64').replace(/\+/g, '-').replace(/\//g, '_');

  var params = {
    auth: jwtClient,
    userId: "myaddress@gmail.com",
    resource: {
      raw: base64EncodedEmailSafe
    }
  };

  gmail.users.messages.send(params, function(err, res) {
    if (err) console.log('error sending mail', err);
    else console.log('great success', res);
  });
}

What am I missing?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

Sort of surprised to have found my own post about this issue having the same trouble and googling for my other project after years.

@kunokdev Yes. We need to set “subject (or sub) field” of google.auth.JWT which should be identical to a gmail address to be managed. This is rarely explained for g-suite/gmail API, and an only document I could find is:

https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Additional claims

In some enterprise cases, an application can request permission to act on behalf of a particular user in an organization. Permission to perform this type of impersonation must be granted before an application can impersonate a user, and is usually handled by a domain administrator. For more information on domain administration, see Managing API client access.

To obtain an access token that grants an application delegated access to a resource, include the email address of the user in the JWT claim set as the value of the sub field.

Unfortunately, this field does not accept array, so to manage multiple gmail address under the same domain, you need to obtain each jwtClients.

After all, a single service account with a single credential works for multiple gmail addresses under the same domain.

https://developers.google.com/admin-sdk/directory/v1/guides/delegation

Here is a very clean node.js sample to get started.

(() => {
  "use strict";
  //============================
  const google = require('googleapis');
  const google_key = require("/your/google-key.json"); //download from google API console

  const jwtClient = new google.auth.JWT(
    google_key.client_email,
    null,
    google_key.private_key,
    ["https://mail.google.com/"], //full access for now, you can restrict more
    'adm@yourdomain.com' // subject (or sub) <-----------------------
  );

  jwtClient.authorize((err, tokens) => (err
    ? console.log(err)
    : (() => { //--------------------------------
      console.log("Google-API Authed!");
      const gmail = google.gmail({
        version: "v1",
        auth: jwtClient
      });
      gmail.users.messages.list({
        userId: 'adm@yourdomain.com'
      }, (err, messages) => {
        //will print out an array of messages plus the next page token
        console.log(err);
        console.dir(messages);
      });
    })() //--------------------------------
  //======================
  ));
//============================
})();

Ah, right. You cannot authorize Gmail API requests with JWT, you must use OAuth 2.0 because it needs to be auth’d to a specific user. Or else you’d be able to do some really shady things like send messages impersonating someone else. The Google APIs Explorer is authenticated with OAuth 2.0 that’s why it works. See https://developers.google.com/gmail/api/auth/about-auth for more information.

you save me

@kenokabe Did you find any solution yet? @ryanseys I am trying to fetch all emails under our company. I have full admin account. Is it true that I can’t do this by using JWT auth in node.js client lib? And if so, which is alternative method to avoid any direct user interaction ?