25

Disclaimer:

  • I have followed Google's own Node.js quickstart guide and successfully connect and use the gmail.users.labels.list() functionality.
  • I have checked for questions/answers here, like this one (that is not using the Node.js API I am asking about), or this one (similar to this one) which apparently is the same problem I have but the solution does not work.

My problem:

When using Google's Node.js API I get a error trying to send a email. The error is:

{
    "code": 403,
    "errors": [{
        "domain": "global",
        "reason": "insufficientPermissions",
        "message": "Insufficient Permission"
    }]
}

My setup:

fs.readFile(secretlocation, function processClientSecrets(err, content) {
    if (err) {
        console.log('Error loading client secret file: ' + err);
        return;
    }
    authorize(JSON.parse(content), sendMessage);
});

function sendMessage(auth) {
    var raw = makeBody('myrealmail@gmail.com', 'myrealmail@gmail.com', 'subject', 'message test');
    gmail.users.messages.send({
        auth: auth,
        userId: 'me',
        message: {
            raw: raw
        }
    }, function(err, response) {
        res.send(err || response)
    });
}

The function processClientSecrets is from the Google guide i mentioned above. It reads my .json file that has my access_token and refresh_token. The makeBody function is a to make a encoded body message.

In the config variabels I have also:

var SCOPES = [
    'https://mail.google.com/',
    'https://www.googleapis.com/auth/gmail.modify',
    'https://www.googleapis.com/auth/gmail.compose',
    'https://www.googleapis.com/auth/gmail.send'
];

Why it should work:

  • the authorization process works for the gmail.users.labels.list() method.
  • the message body I'm testing works if I test it at Google's test page.

My question:

Is my setup wrong? Have there been changes in the API? What am I missing?

Community
  • 1
  • 1
Sergio
  • 28,539
  • 11
  • 85
  • 132
  • Make sure you have the required scopes authorized: https://developers.google.com/gmail/api/v1/reference/users/messages/send#auth – AboulEinein Dec 31 '15 at 12:13
  • @AboulEinein I do, but still doesn't work, thanks for pointing. I had to have it also when testing in the [Google tool](https://developers.google.com/apis-explorer/#p/gmail/v1/gmail.users.messages.send). Currently I have these _Auth scopes held_: `https://mail.google.com/, gmail.compose, gmail.modify, gmail.send` – Sergio Dec 31 '15 at 12:24
  • Go to the [Oauth Playground](https://developers.google.com/oauthplayground/), authorize with the Gmail Scopes, and take the `access token` and `refresh token` and put these in your `.json`-file. Do you still get an error? – Tholle Jan 01 '16 at 15:41
  • @Tholle thanks for checking this out. Did that step you suggested and now the error is different: `{"code":400,"errors":[{"domain":"global","reason":"invalidArgument","message":"'raw' RFC822 payload message string or uploading message via /upload/* URL required"}]}` – Sergio Jan 01 '16 at 16:43
  • @Sergio Hmm, that's annoying. Have you tried making the `raw`-string url safe (replace all `+` with `-` and all `/` with `_`)? – Tholle Jan 01 '16 at 17:42
  • 1
    @Sergio Darn :( Then I don't know. Hopefully someone else can chime in. – Tholle Jan 01 '16 at 21:51
  • 1
    @Tholle found my problem(s). Thanks for checking this! – Sergio Jan 02 '16 at 07:15
  • 1
    @Sergio Great! No problem. :) – Tholle Jan 02 '16 at 12:05

2 Answers2

31

Ok, so I found the problem(s).

Problem #1 While following the Node.js quickstart guide the example in that tutorial has

var SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];

And when I got the .json that looks like:

{
    "access_token": "xxx_a_long_secret_string_i_hided_xxx",
    "token_type": "Bearer",
    "refresh_token": "xxx_a_token_i_hided_xxx",
    "expiry_date": 1451721044161
}

those tokens where produced taking into account only the auth/gmail.readonly scope in the tutorial code.

So I deleted the first .json, added the scopes from my final scope array (i posted in the question) and ran the tutorial setup again, receiving a new token.

Problem #2

In the object passed to the API I was sending:

{
    auth: auth,
    userId: 'me',
    message: {
        raw: raw
    }
}

but that is wrong, message key should be called resource.


Final setup:

This is what I added to the tutorial's code:

function makeBody(to, from, subject, message) {
    var str = ["Content-Type: text/plain; charset=\"UTF-8\"\n",
        "MIME-Version: 1.0\n",
        "Content-Transfer-Encoding: 7bit\n",
        "to: ", to, "\n",
        "from: ", from, "\n",
        "subject: ", subject, "\n\n",
        message
    ].join('');

    var encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
        return encodedMail;
}

function sendMessage(auth) {
    var raw = makeBody('myrealemail@gmail.com', 'myrealemail@gmail.com', 'test subject', 'test message');
    gmail.users.messages.send({
        auth: auth,
        userId: 'me',
        resource: {
            raw: raw
        }
    }, function(err, response) {
        res.send(err || response)
    });
}

And call everything with:

fs.readFile(secretlocation, function processClientSecrets(err, content) {
    if (err) {
        console.log('Error loading client secret file: ' + err);
        return;
    }
    // Authorize a client with the loaded credentials, then call the
    // Gmail API.
    authorize(JSON.parse(content), sendMessage);
});
Sergio
  • 28,539
  • 11
  • 85
  • 132
  • 1
    how to attach html payload? – Arjun Kava Jan 30 '17 at 09:55
  • 4
    @ArjunKava just set `Content-Type: text/html;` instead of `Content-Type: text/plain;`. – Jyotman Singh May 03 '17 at 08:24
  • thank u @JyotmanSingh but i also need to send attachments within payload which are my local PC files , i have tried with mime content type but still not working. – Arjun Kava May 05 '17 at 13:03
  • 2
    Please, can you explain why you need to do some replacements over the base64 raw message. Thanks – IcanDivideBy0 May 08 '17 at 07:01
  • 2
    I also would like to know why they replacements of the base64 content. Also, where did you find documentation about raw content is inside resource attribute. – grabbag Feb 21 '18 at 18:24
  • this code has given me an this error : `'raw' RFC822 payload message string or uploading message via /upload/* URL required [400].`. This can be fixed by updating google-auth-api to the latest verstion. https://github.com/google/google-api-nodejs-client/issues/979 – Arvind Sridharan Mar 16 '18 at 12:45
  • 1
    You forgot `const gmail = google.gmail({version: 'v1', auth});` in `sendMessage()`. Also `res` in `sendMessage()` was not defined. After commenting it out, I got this error: `Error: Insufficient Permission`. Anyone else having this issue? – Soubriquet Apr 25 '19 at 18:29
  • hello, How can I get an inbox email list? please advice. I already fetched list labels – Vishnu Chauhan Mar 31 '20 at 10:16
8

So for anyone looking at this trying to get a test email sent from their API but cant get this work heres what you gotta do:

Step 1: Replace the

var SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];

with this:

var SCOPES = [
    'https://mail.google.com/',
    'https://www.googleapis.com/auth/gmail.modify',
    'https://www.googleapis.com/auth/gmail.compose',
    'https://www.googleapis.com/auth/gmail.send'
];

Step 2: At the end of googles sample code add this:

function makeBody(to, from, subject, message) {
    var str = ["Content-Type: text/plain; charset=\"UTF-8\"\n",
        "MIME-Version: 1.0\n",
        "Content-Transfer-Encoding: 7bit\n",
        "to: ", to, "\n",
        "from: ", from, "\n",
        "subject: ", subject, "\n\n",
        message
    ].join('');

    var encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
        return encodedMail;
}

function sendMessage(auth) {
    var raw = makeBody('Receiverofyouremail@mail.com', 'whereyouaresendingstufffrom@gmail.com', 'This is your subject', 'I got this working finally!!!');
    const gmail = google.gmail({version: 'v1', auth});
    gmail.users.messages.send({
        auth: auth,
        userId: 'me',
        resource: {
            raw: raw
        }
    
    }, function(err, response) {
        return(err || response)
    });
}

fs.readFile('credentials.json', function processClientSecrets(err, content) {
    if (err) {
        console.log('Error loading client secret file: ' + err);
        return;
    }
    // Authorize a client with the loaded credentials, then call the
    // Gmail API.
    authorize(JSON.parse(content), sendMessage);
});

Step 3(Optional)

Delete this line:

authorize(JSON.parse(content), listLabels);

And these:

/**
 * Lists the labels in the user's account.
 *
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
 function listLabels(auth) {
   const gmail = google.gmail({version: 'v1', auth});
   gmail.users.labels.list({
     userId: 'me',
   }, (err, res) => {
     if (err) return console.log('The API returned an error: ' + err);
     const labels = res.data.labels;
     if (labels.length) {
       console.log('Labels:');
       labels.forEach((label) => {
         console.log(`- ${label.name}`);
       });
     } else {
       console.log('No labels found.');
     }
   });
 }

(So you don't get the random labels in your console)

Sujeet Sinha
  • 2,417
  • 2
  • 18
  • 27
Vincent Morris
  • 640
  • 1
  • 9
  • 26