Failed sending mail through google api - node.js

I've been trying to send emails using Google's Gmail API and I kept getting the following error:
The API returned an error: Error: 'raw' RFC822 payload message string or uploading message via /upload/* URL required
I did the setup using the starter code Google gave for NodeJS (documentation).
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const Base64 = require('js-base64').Base64;
// ...
// create the email string
const emailLines = [];
emailLines.push("From: \"My Name\" <MY_EMAIL#gmail.com>");
emailLines.push("To: YOUR_EMAIL#uw.edu");
emailLines.push('Content-type: text/html;charset=iso-8859-1');
emailLines.push('MIME-Version: 1.0');
emailLines.push("Subject: New future subject here");
emailLines.push("");
emailLines.push("And the body text goes here");
emailLines.push("<b>And the bold text goes here</b>");
const email =email_lines.join("\r\n").trim();
// ...
function sendEmail(auth) {
const gmail = google.gmail('v1');
const base64EncodedEmail = Base64.encodeURI(email);
base64EncodedEmail.replace(/\+/g, '-').replace(/\//g, '_')
console.log(base64EncodedEmail);
gmail.users.messages.send({
auth: auth,
userId: "me",
resource: {
raw: base64EncodedEmail
}
}, (err, response) => {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
console.log(response);
});
}
You can picture auth as an object:
{
transporter: ...,
_certificateCache: ...,
_certificateExpiry: ...,
_clientId: ...,
_clientSecret: ...,
_redirectUri: ...,
_opts: {},
credentials: {
access_token: ...,
refresh_token: ...,
token_type: 'Bearer',
expiry_date: 1517563087857
}
}
What matters is the access_token.
I've already tried the solutions proposed which are listed here:
StackOverflow: Failed sending mail through google api with javascript
ExceptionsHub: Failed sending mail through google api in nodejs
StackOverflow: Gmail API for sending mails in Node.js
But none of them worked. However, when I copied and pasted the encoded string onto the Playground of Google's own documentation, and it works (documentation):
Therefore, I changed to using a fetch request instead, and it also worked.
fetch(`https://www.googleapis.com/gmail/v1/users/me/messages/send`, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + `the_access_token_in_auth_obj`,
'HTTP-Version': 'HTTP/1.1',
'Content-Type': 'application/json',
},
body: JSON.stringify({
raw: base64EncodedEmail
})
})
.then((res) => res.json())
.then((res) => console.info(res));
Can anyone explain why it happened? Is it a bug from googleapi or am I missing something?

I ran into the same "RFC822 payload message string or uploading message via /upload/* URL required". The quickstart/nodejs sample specifies a version of google-auth-library that caused this error. 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('xxxxxxxx#hotmail.com', 'xxxxxxx#gmail.com', '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, …}

Adding to #grabbag's answer, which excluded the definition for store_token. As the Drive quickstart notes, that function can be defined as follows:
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}

Related

I got error Recipient address required with Gmail API

I want to send an email with Gmail API.
Document say Gmail API require RFC2822 formatted and base64 encoded string.
So I write email contents and pass it to raw property.
But I got error: Recipient address required.
How can I fix this?
Here is my code.
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/gmail.send'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Gmail API.
authorize(JSON.parse(content), sendGmail);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function sendGmail(auth){
const makeBody = (params) => {
params.subject = new Buffer.from(params.subject).toString("base64");
const str = [
'Content-Type: text/plain; charset=\"UTF-8\"\n',
'MINE-Version: 1.0\n',
'Content-Transfer-Encoding: 7bit\n',
`to: ${params.to} \n`,
`from: ${params.from} \n`,
`subject: =?UTF-8?B?${params.subject}?= \n\n`,
params.message
].join(' ');
return new Buffer.from(str).toString('base64').replace(/\+/g,'-').replace(/\//g,'_');
}
const messageBody = `
this is a test message
`;
const raw = makeBody({
to : 'foo#gmail.com',
from : 'foo#gmail.com',
subject : 'test title',
message:messageBody
});
jj
const gmail = google.gmail({version:'v1',auth:auth});
gmail.users.messages.send({
userId:"me",
resource:{
raw:raw
}
}).then(res => {
console.log(res);
});
}
resulr:
Error: Recipient address required
edit : show whole code. This code still get the same error.
This is almost Google's sample and I think bug is in my code.
I add sendGnail method and edit authorize(JSON.parse(content), listLabels); to authorize(JSON.parse(content), sendGmail); ,change SCOPES ,and delete listLabels method.
(listLabels method worked fine.)
After execute listLabels method, I change SCOPES and re-create token.json.
After get Labels, I change
Here is sample
https://developers.google.com/gmail/api/quickstart/nodejs?hl=ja
How about this modification?
From:
].join(' ');
To:
].join('');
Note:
I think that the script will work by above modification. But as one more modification point, how about modifying from 'Content-Type: text/plain; charaset=\"UTF-8\"\n', to 'Content-Type: text/plain; charset=\"UTF-8\"\n',?
If this was not the direct solution, I apologize.
Edit:
I modified the function of sendGmail in your script.
Modified script:
function sendGmail(auth) {
const makeBody = params => {
params.subject = new Buffer.from(params.subject).toString("base64");
const str = [
'Content-Type: text/plain; charset="UTF-8"\n',
"MINE-Version: 1.0\n",
"Content-Transfer-Encoding: 7bit\n",
`to: ${params.to} \n`,
`from: ${params.from} \n`,
`subject: =?UTF-8?B?${params.subject}?= \n\n`,
params.message
].join(""); // <--- Modified
return new Buffer.from(str)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_");
};
const messageBody = `
this is a test message
`;
const raw = makeBody({
to: "foo#gmail.com",
from: "foo#gmail.com",
subject: "test title",
message: messageBody
});
const gmail = google.gmail({ version: "v1", auth: auth });
gmail.users.messages.send(
{
userId: "me",
resource: {
raw: raw
}
},
(err, res) => { // Modified
if (err) {
console.log(err);
return;
}
console.log(res.data);
}
);
}

How to send an email with attachment using Gmail API in Node.js?

I'm new to Node.js and I'm trying to create a mail box using the Gmail API everything works fine except uploading an attachment in email. I found examples with Java, Python and C#, yet I can't find any documentation with node about it.
Any tips would be very much appreciated.
Here is my code:
function makeBody(to, from, subject, message) {
var str = ["Content-Type: multipart/mixed; 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,
file
].join('');
var encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
return encodedMail;
}
function sendMessage(auth) {
var raw = makeBody(tap, 'me', response.subject, response.content, response.files);
gmail.users.messages.send({
auth: auth,
userId: 'me',
resource: {
raw: raw
}
}, function (err, response) {
if (err) {
console.log('Error ' + err);
return;
}
if (response) {
res.sendFile(__dirname + '/boite.html')
}
});
}
This might go a little bit to late, anyway i will take the time in the case someone later wants an alternative.
The major problem with Moxched approach was that probably he needed to take a closer look at the MIME spec (which was a big pain for me) to understand better a few things that are necessary to send attachments.
From where i stand, to be able to use the gmail API to send attachments and a lot of other stuff you have to build the all request according to MIME spec, to do that you need to understand how things in MIME work including boundaries.
Joris approach works but ends up not using the nodeJS lib to send the email. The reason why he wasnt able to use the answer from the gmail-api-create-message-body package with the gmail API, is because for some reason this lib generates at the top of its MIME message the following:
'Content-Type: multipart/related; boundary="foo_bar_baz"',
`In-Reply-To: fakeemail#gmail.com`,
`References: `,
`From: fakeemail2#gmail.com`,
`Subject: SUBJECT`,
`MIME-Version: 1.0`,
'',
`--foo_bar_baz`,
`Content-Type: application/json; charset="UTF-8"`,
'',
`{`,
`}`,
'',
`--foo_bar_baz`,
`Content-Type: message/rfc822`,
'',
...
For some reason the gmailAPI doesn't like this...
My suggestion is to understand a little bit better the MIME spec, a really easy way to that is to use some old reverse engineering, for that i suggest looking at the replies from gmail-api-create-message-body and mail-composer from nodemailer.
Using nodemailer/lib/mail-composer you will be able to generate the necessary MIME message according to the MIME spec with ease, it includes attachment support and all bunch of other stuff. The MIME messages generated are compatible with the Gmail API. I leave a working example, based on the examples of NodeJS docs, that sends an email with 2 attachments.
Hope this helps!
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const {google} = require('googleapis');
const MailComposer = require('nodemailer/lib/mail-composer');
// If modifying these scopes, delete token.json.
const SCOPES = [
'https://mail.google.com',
'https://www.googleapis.com/auth/gmail.readonly'
];
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Gmail API.
//authorize(JSON.parse(content), listLabels);
authorize(JSON.parse(content), sendEmail);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function sendEmail(auth) {
// ----------nodemailer test----------------------------------------------------
let mail = new MailComposer(
{
to: "FAKE_EMAIL#gmail.com",
text: "I hope this works",
html: " <strong> I hope this works </strong>",
subject: "Test email gmail-nodemailer-composer",
textEncoding: "base64",
attachments: [
{ // encoded string as an attachment
filename: 'text1.txt',
content: 'aGVsbG8gd29ybGQh',
encoding: 'base64'
},
{ // encoded string as an attachment
filename: 'text2.txt',
content: 'aGVsbG8gd29ybGQh',
encoding: 'base64'
},
]
});
mail.compile().build( (error, msg) => {
if (error) return console.log('Error compiling email ' + error);
const encodedMessage = Buffer.from(msg)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const gmail = google.gmail({version: 'v1', auth});
gmail.users.messages.send({
userId: 'me',
resource: {
raw: encodedMessage,
}
}, (err, result) => {
if (err) return console.log('NODEMAILER - The API returned an error: ' + err);
console.log("NODEMAILER - Sending email reply from server:", result.data);
});
})
// ----------nodemailer test----------------------------------------------------
}
Being stuck on the same problem, I managed to build a solution by grabbing stuff left and right.
what you need to use is the npm package gmail-api-create-message-body
npm package page
const body = createBody({
headers:{
To:(msg.to.name) + " <" + msg.to.email + ">",
From:(msg.from.name) + " <" + msg.from.email + ">",
Subject:msg.subject
},
textHtml:msg.body.html,
textPlain:msg.body.text,
attachments:msg.files
})
The files are an array of the following format. This is just an example:
files: [{
type: "image/jpeg",
name: "id1.jpg",
data:base64ImageData
}, {
type: "image/jpeg",
name: "id2.jpg",
data: base64ImageData
}]
Next I needed to mix 2 api's. I wanted to do everything through the Google API's but that did not work and I didn't want to waste hours understanding why (and their documentation + examples for node are a disaster)
In order to do the call we need the authentication token. This can be found using the npm package google-auth-library
await oauth2Client.getAccessToken()
The full details of how to OAuth2 with Google are out of scope for this answer I think.
Next we need to actually send the mail. Impossible for me to get it to work with the official Gmail api (kept getting Error: Recipient address required), so I used request-promise as shown in the example of gmail-api-create-message-body
await rp({
method: 'POST',
uri: 'https://www.googleapis.com/upload/gmail/v1/users/me/messages/send',
headers: {
Authorization: `Bearer ${oauth2Client.credentials.access_token}`,
'Content-Type': 'multipart/related; boundary="foo_bar_baz"'
},
body: body
});
And this al worked perfectly.
There's an instruction with regard to this in Creating messages with attachments:
Creating a message with an attachment is like creating any other message, but the process of uploading the file as a multi-part MIME message depends on the programming language.
For the NodeJS sample reference, check this SO Post.
I was able to do it by using nodemailer mail composer.
here is the complete source code based on the nodejs quick-start:
https://developers.google.com/gmail/api/quickstart/nodejs
const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const {authenticate} = require('#google-cloud/local-auth');
const {google} = require('googleapis');
const OAuth2 = google.auth.OAuth2;
const REDIRECT_URI = ["http://localhost"]
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.compose'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = path.join(process.cwd(), 'token.json');
const CREDENTIALS_PATH = path.join(process.cwd(), 'google_credentials.json');
const nodemailer = require("nodemailer");
const MailComposer = require("nodemailer/lib/mail-composer");
async function loadSavedCredentialsIfExist() {
try {
const content = await fs.readFile(TOKEN_PATH);
const credentials = JSON.parse(content);
return google.auth.fromJSON(credentials);
} catch (err) {
return null;
}
}
async function saveCredentials(client) {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content);
const key = keys.installed || keys.web;
const payload = JSON.stringify({
type: 'authorized_user',
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
});
await fs.writeFile(TOKEN_PATH, payload);
}
async function authorize() {
let client = await loadSavedCredentialsIfExist();
if (client) {
return client;
}
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) {
await saveCredentials(client);
}
return client;
}
let EMAIL_ADDRESS = process.env.GMAIL_EMAIL_ADDRESS
function streamToString (stream) {
const chunks = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
stream.on('error', (err) => reject(err));
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
})
}
let messagePayload = {
subject: "Exploring Gmail API",
text: "Hi, this is a test email from Node.js using Gmail API",
to: "email#gmail.com",
from: EMAIL_ADDRESS,
attachments: [{filename: 'doc.pdf', path: './doc.pdf'}]
}
async function sendEmail(auth) {
const gmail = google.gmail({version: 'v1', auth});
var mail = new MailComposer(messagePayload);
var stream = mail.compile().createReadStream();
const messageResult = await streamToString(stream)
const encodedMessage = Buffer.from(messageResult).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const res = await gmail.users.messages.send({
userId: 'me',
requestBody: {
raw: encodedMessage,
},
});
console.log(res.data);
return res.data;
}
authorize().then(sendEmail).catch(console.error);

Error: Missing end time. Google Calendar API with Node JS

I am realising example from Google Calendar API instruction web page. I put the code into example.js file and start it with node example.js in command line.
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/calendar-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/calendar'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'calendar-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.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
// Google Calendar API.
authorize(JSON.parse(content), listEvents);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 auth.OAuth2(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);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
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);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try **strong text**{
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Lists the next 10 events on the user's primary calendar.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listEvents(auth) {
var calendar = google.calendar('v3');
calendar.events.list({
auth: auth,
calendarId: 'primary',
timeMin: (new Date()).toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: 'startTime'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var events = response.items;
if (events.length == 0) {
console.log('No upcoming events found.');
} else {
console.log('Upcoming 10 events:');
for (var i = 0; i < events.length; i++) {
var event = events[i];
var start = event.start.dateTime || event.start.date;
console.log('%s - %s', start, event.summary);
}
}
});
}
everthing is working allrigth.
But when I try to insert event to Google calendar with this code.
authorize(JSON.parse(content), addEvent);
function addEvent(auth) {
var event = {
'summary': 'Google I/O 2015',
'location': '800 Howard St., San Francisco, CA 94103',
'description': 'A chance to hear more about Google\'s developer products.',
'start': {
'dateTime': '2015-05-28T09:00:00-07:00',
'timeZone': 'America/Los_Angeles',
},
'end': {
'dateTime': '2015-05-28T17:00:00-07:00',
'timeZone': 'America/Los_Angeles',
},
'recurrence': [
'RRULE:FREQ=DAILY;COUNT=2'
],
'attendees': [
{'email': 'lpage#example.com'},
{'email': 'sbrin#example.com'},
],
'reminders': {
'useDefault': false,
'overrides': [
{'method': 'email', 'minutes': 24 * 60},
{'method': 'popup', 'minutes': 10},
],
},
};
var calendar = google.calendar('v3');
calendar.events.insert({
auth: auth,
calendarId: 'primary',
resource: event,
}, function(err, event) {
if (err) {
console.log('There was an error contacting the Calendar service: ' + err);
return;
}
console.log('Event created: %s', event.htmlLink);
});
I invariably get:
There was an error contacting the Calendar service: Error: Missing end time.
What I am doing wrong.
I had the same issue. I had a version of the google-auth-library that wasn't compatible with the most recent googleapis version. Check your versions - you might need to upgrade and migrate over your google-auth-library which is why the example above doesnt work for you.
Here's the edits you might have to do if this is your issue:
https://github.com/google/google-auth-library-nodejs/releases/tag/v1.0.0
I am currently having the same error as this one, using codes from examples and from #noogui. I found this library working as expected although requires a lot of configuration node-google-calendar.
I also hope Google Calendar API will work soon.
Try mine:
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/calendar-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/calendar'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'calendar-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.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
// Google Calendar API.
authorize(JSON.parse(content), insertEvent);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 auth.OAuth2(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);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
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);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
function insertEvent(auth) {
var event = {
'summary': 'This is a test Calendar invite using NodeJS',
'location': 'Under the Bridge',
'description': 'I dont ever wanna feel Like I did that day But take me to the place I love Take me all the way',
'start': {
'dateTime': '2018-02-25T12:58:05-07:00',
'timeZone': 'America/Los_Angeles',
},
'end': {
'dateTime': '2018-02-26T06:00:00-07:00',
'timeZone': 'America/Los_Angeles',
},
'recurrence': [
'RRULE:FREQ=DAILY;COUNT=2'
],
'attendees': [
{'email': 'rhcp#gmail.com'}
],
'reminders': {
'useDefault': true
}
};
var calendar = google.calendar('v3');
calendar.events.insert({
auth: auth,
calendarId: 'primary',
resource: event,
}, function(err, event) {
if (err) {
console.log('There was an error contacting the Calendar service: ' + err);
return;
}
console.log('Event created: %s', event.htmlLink);
});
}
Proof the sample Quickstart is working. Events are inserted in my calendar:
googleapis#24.0.0 and google-auth-library#0.12.0 worked for me

Google API Google Drive Node.js can´t upload file oAuth2

Hey I tried for over 6h now and nothing working. I came this far..
I created a successfull connection to my api with this default code from the google api pages. I also enabled advanced features Google Drive at my console google page. I successfull created the token and it´s now stored at C:\Users\Administrator.credentials
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/drive-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'drive-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.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
// Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 auth.OAuth2(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);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
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);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Lists the names and IDs of up to 10 files.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listFiles(auth) {
var service = google.drive('v3');
service.files.list({
auth: auth,
pageSize: 10,
fields: "nextPageToken, files(id, name)"
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var files = response.files;
if (files.length == 0) {
console.log('No files found.');
} else {
console.log('Files:');
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log('%s (%s)', file.name, file.id);
}
}
});
}
When I rerun this script I get
Files:
sample file.mp4 (123456789sampelcode)
projektname (123456789sampelcode)
Getting started (123456789sampelcode)
To the connection now worked with the token because I don´t need to do reenter something. I really don´t understand now how to upload a file. When i try to include my own upload function I can´t use it because I can´t combine it with the login process. I saw that
function listFiles(auth) {
}
Was default execuded after the login. So I thought I enter my code in this function it would work . So created this
function listFiles(auth) {
var drive = google.drive('v3');
var fileMetadata = {
'name': 'photo.jpg'
};
var media = {
mimeType: 'image/jpeg',
body: fs.createReadStream('./photo.jpg')
};
drive.files.create({
resource: fileMetadata,
media: media,
auth: auth,
fields: 'id'
}, function(err, file) {
if(err) {
// Handle error
console.log(err);
} else {
console.log('File Id: ', file.id);
}
});
}
But nothing happen. My Terminal window is endless loading. No error no nothing.
Version 2 - This is my second version . also no log no nothing just endless loading. I turned of firewall so no way thats there a problem with the firewall or antivirus or somthing because in the default script I can see the files from my googledrive
const fs = require('fs');
const readline = require('readline');
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const drive = google.drive('v3');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/drive-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/drive'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'drive-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.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
// Drive API.
//authorize(JSON.parse(content), listFiles);
authorize(JSON.parse(content), real_upload_files);
});
/*
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
//console.log('5')
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
// console.log('4')
}
});
} // function authorize(credentials, callback) {
function real_upload_files(auth) {
var fileMetadata = {
'name': 'photo.jpg'
};
var media = {
mimeType: 'image/jpeg',
body: fs.createReadStream('./photo.jpg')
};
drive.files.create({
resource: fileMetadata,
media: media,
auth: auth,
fields: 'id'
}, function(err, file) {
if(err) {
// Handle error
console.log(err);
} else {
console.log('File Id: ', file.id);
}
});
} // function real_upload_files(auth) {
Also If I would change auth: auth to auth: oauth2Client and change it also in the function then will be same result endless loading screen.
Can somebody explain me pls how to upload files or do other stuff with the oAuth2. I find as node.js newbie that there are to less sample codes to find for for node.js .. Please help me guys I strugggle so hard with this.. please help :D With dropbox it takes 10 min but there I easy used a API Key ..
I think that your script is no wrong. In my environment, your script works fine. So I propose following 2 modifications.
Modification points :
What terminal do you use for this? It seems that from C:\Users\Administrator.credentials of your question, you use windows OS. If it's so, can you use cmd.exe for this? If you don't use windows. Please don't change the terminal.
How about the use of readFile() for reading the file? readFile() can read whole data in the file to the memory. So I thought that the error might be avoided. But if the file size is large, I recommend to use createReadStream(). I confirmed that readFile() and createReadStream() can use to upload file to Google Drive using cmd.exe as a terminal with windows OS.
Modified script :
function real_upload_files(auth) {
fs.readFile('./photo.jpg', function(err, content){
var fileMetadata = {
'name': 'photo.jpg',
};
var media = {
mimeType: 'image/jpeg',
body: new Buffer(content, 'binary'),
};
drive.files.create({
resource: fileMetadata,
media: media,
auth: auth,
fields: 'id',
}, function(err, file) {
if (err) {
console.log(err);
} else {
console.log('File Id: ', file.id);
}
});
});
}
I don't know whether these lead to the solution. I'm sorry.

Gmail API for sending mails in Node.js

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

Resources