2

My nodejs program fails to send messages using the Gmail api.

The solution from Gmail API for sending mails in Node.js does not work for me.

I encode an email with

var {google} = require('googleapis');

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

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

    return encodedMail;
}

Then go to the Google API explorer https://developers.google.com/apis-explorer/#p/ enter gmail.users.messages.send and the string generated from the above make_body.

enter image description here

An email will be successfully sent. So I know the above encoding is ok.

When my program tried to send using the following, it fails with error

Error: 'raw' RFC822 payload message string or uploading message via /upload/* URL required

function sendMessage(auth) {
    var gmail = google.gmail('v1');
    var raw = makeBody('john g <[email protected]>', 'john g<[email protected]>', 'test subject', 'test message #2');

    gmail.users.messages.send({
        auth: auth,
        userId: 'me',
        resource: {
            raw: raw
        }

    }, function(err, response) {
        console.log(err || response)
    });
}

The auth token is good since I can call gmail.users.labels.list and I use the same authorization when using the API explorer.

Q1: Does anyone know why the above does not work?

Q2: Gmail API for sending mails in Node.js does not explain why the raw email message is wrapped inside a resource field. I tried simply raw and it did not help.

This fails.

gmail.users.messages.send({
        auth: auth,
        userId: 'me',
        resource: {
            raw: raw
        }

    }, function(err, response) {
        console.log(err || response)
    });

and so does

gmail.users.messages.send({
    auth: auth,
    userId: 'me',
    raw: raw

}, function(err, response) {
    console.log(err || response)
});

and so does this GMAIL API for sending Email with attachment

gmail.users.messages.send({
    auth: auth,
    userId: 'me',
    data: raw

}, function(err, response) {
    console.log(err || response)
});

Does anyone know where its documented how to pass the "requested body" the api explorer is asking for?

Q3: Why does the google api need substitutions in the base64 encoding?

I tried encoding using

const Base64 = require("js-base64").Base64
var encodedMail = Base64.encode(str);

When I feed this into the API explorer, I get the error

"message": "Invalid value for ByteString:

6
  • developers.google.com/gmail/api/v1/reference/users/messages/… gives an example in the javascript tab but it says "Does not use a client library.". But it does not say what it is using. It makes a call gapi.client.gmail.users.messages.send but does not indicate what value gapi has. Commented Feb 21, 2018 at 21:03
  • Before going further, can you check if you can get some information in this SO post and this another SO post? Commented Feb 22, 2018 at 10:59
  • Thank you for finding some more very similar issues. The first solution you found suggests using resource: raw: is what I tried. The second solution you found suggest multipart/related, but I'm trying to send a simple "hello world" no attachment and the content type works when I past the encoded string into the playground. This API is definitely could use some documentation and some examples for something that should be simple. Commented Feb 22, 2018 at 13:53
  • Like I said in the issue tracker, you can refer to this SO post Commented Feb 22, 2018 at 13:56
  • stackoverflow.com/questions/48579566 appears to be a virtually identical problem, unsolved, and the author suggests not using googleapi API? I did not see this one when I posted my problem. Commented Feb 22, 2018 at 14:00

2 Answers 2

4

Ohai! For others that stumble here, a few things. First - we have a complete end to end sample of sending mail now here:

https://github.com/google/google-api-nodejs-client/blob/master/samples/gmail/send.js

Second, the answer above is mostly right :) Instead of installing the latest version of google-auth-library... just remove it from your package.json all together. The getting started guide was very, very wrong (it has since been fixed). googelapis brings in it's own compatible version of google-auth-library, so you really don't want to mess with that by installing your own version :)

Sign up to request clarification or add additional context in comments.

Comments

2

The quickstart specifies:

npm install google-auth-library@0.* --save

When I changed this to

npm install google-auth-library -- save

it pulled in version 1.3.1 vs 0.12.0. Everything started working once I changed the code to account for the breaking changes. The latest version of googleapis also has breaking changes. Here is my tweaks to the quickstart:

package.json

 ....
  "dependencies": {
    "google-auth-library": "^1.3.1",
    "googleapis": "^26.0.1"
  }

quickstart.js

var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
const {GoogleAuth, JWT, OAuth2Client} = require('google-auth-library');


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'
];

var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
    process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'gmail-nodejs-quickstart.json';

function authorize(credentials, callback) {
    var clientSecret = credentials.installed.client_secret;
    var clientId = credentials.installed.client_id;
    var redirectUrl = credentials.installed.redirect_uris[0];
    var auth = new GoogleAuth();
    var oauth2Client = new OAuth2Client(clientId, clientSecret, redirectUrl);

    // Check if we have previously stored a token.
    fs.readFile(TOKEN_PATH, function (err, token) {
        if (err) {
            getNewToken(oauth2Client, callback);
        } else {
            oauth2Client.credentials = JSON.parse(token);
            callback(oauth2Client);
        }
    });
}

function getNewToken(oauth2Client, callback) {
    var authUrl = oauth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: SCOPES
    });
    console.log('Authorize this app by visiting this url: ', authUrl);
    var rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });
    rl.question('Enter the code from that page here: ', function (code) {
        rl.close();
        oauth2Client.getToken(code, function (err, token) {
            if (err) {
                console.log('Error while trying to retrieve access token', err);
                return;
            }
            oauth2Client.credentials = token;
            storeToken(token);
            callback(oauth2Client);
        });
    });
}


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 gmail = google.gmail('v1');
    var raw = makeBody('[email protected]', '[email protected]', 'test subject', 'test message');
    gmail.users.messages.send({
        auth: auth,
        userId: 'me',
        resource: {
            raw: raw
        }
    }, function(err, response) {
        console.log(err || response)
    });
}

const secretlocation = 'client_secret.json'

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

Now when I run, I get the response

Object {status: 200, statusText: "OK", headers: Object, config: Object, request: ClientRequest, …}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.