node.jsgmail-apigoogle-api-nodejs-client

Gmail API for sending mails in Node.js


Disclaimer:

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:


My question:

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


Solution

  • 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);
    });