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);
}
);
}
Related
I have authenticated against a Youtube Account and have successfully gotten the access token. Now I wish to fetch the user's Youtube Channels using the code below:
async fetchYoutubeChannels() {
let accessToken = ....;
const authCredentials = accessToken;
const oauth2Client = initOAuth2Client(); //Oauth2 Client initialized at the top with secret, redirect url and and client id
oauth2Client.setCredentials(authCredentials);
oauth2Client.on('tokens', async (tokens) => {
if (tokens.refresh_token) {
//Save new token
}
});
let service = google.youtube('v3');
service.channels.list({
auth: oauth2Client,
part: 'snippet,contentDetails,statistics',
}, function (err, response) {
if (err) {
console.log('The Youtube API returned an error: ' + err);
return;
}
let channels = response.data;
console.log(`Retrieved Channels = ${JSON.stringify(channels, null, 2)}`);
});
}
But All I keep getting is this error message:
The Youtube API returned an error: Error: Insufficient Permission
What am I missing?
Any idea would be greatly appreciated.
Thank you.
Error: Insufficient Permission
Means that the user has authorized your application but they have not granted you the permissions you need to access this method.
I am going to assume you are following quickstart nodejs and have just neglected to follow some of the code.
Notice how the get channel method passes the auth parameter which was created as part of the authorization. You appear to be trying to create it manually by setting an access token. You should let the code create the access token as it will be created with the proper scope.
var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
var OAuth2 = google.auth.OAuth2;
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'youtube-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 YouTube API.
authorize(JSON.parse(content), getChannel);
});
/**
* 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 oauth2Client = new 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), (err) => {
if (err) throw err;
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 getChannel(auth) {
var service = google.youtube('v3');
service.channels.list({
auth: auth,
part: 'snippet,contentDetails,statistics',
forUsername: 'GoogleDevelopers'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var channels = response.data.items;
if (channels.length == 0) {
console.log('No channel found.');
} else {
console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
'it has %s views.',
channels[0].id,
channels[0].snippet.title,
channels[0].statistics.viewCount);
}
});
}
From comments
How do I get the username of a youtube account. is it the user display name retrieved during the oauth2 process?
The youtube api is channel based not user based. You are given access to a users channel. Not techincally the user themselves activites might give you some info as to what channel the user authorized you to access but i haven't actually tried.
Error: No filter selected. Expected one of: mySubscribers, id, categoryId, mine, managedByMe, forUsername
Channels: list requires that you send a filter. Check the section Filters (specify exactly one of the following parameters)
I am currently utilising NodeJS and the Gmail API to access my gmail account and return the last email I've received which is obviously only going to be 1 email using this code: (which is a slightly modified version of the example seen here: https://www.codediesel.com/nodejs/how-to-access-gmail-using-nodejs-and-the-gmail-api/
var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
// If modifying these scopes, delete your previously saved credentials
// at TOKEN_DIR/gmail-nodejs.json
var SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
// Change token directory to your system preference
var TOKEN_DIR = ('./');
var TOKEN_PATH = TOKEN_DIR + 'gmail-nodejs.json';
var gmail = google.gmail('v1');
//exports.confirmationEmailFinal;
// 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
// Gmail API.
authorize(JSON.parse(content), getRecentEmail);
});
/**
* 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 OAuth2 = google.auth.OAuth2;
var oauth2Client = new 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.writeFileSync(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Lists the labels in the user's account.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listLabels(auth) {
gmail.users.labels.list({auth: auth, userId: 'me',}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var labels = response.data.labels;
if (labels.length == 0) {
console.log('No labels found.');
} else {
console.log('Labels:');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
console.log('%s', label.name);
}
}
});
}
/**
* Get the recent email from your Gmail account
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function getRecentEmail(auth) {
// Only get the recent email - 'maxResults' parameter
gmail.users.messages.list({auth: auth, userId: 'me', maxResults: 1,}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
// Get the message id which we will need to retreive tha actual message next.
var message_id = response['data']['messages'][0]['id'];
// Retreive the actual message using the message id
gmail.users.messages.get({auth: auth, userId: 'me', 'id': message_id}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
// Access the email body content, like this...
try {
const message_raw = response.data.payload.body.data;
data = message_raw;
buff = new Buffer.from(data, 'base64');
text = buff.toString();
console.log(text);
}
catch (err){
const message_raw = response.data.payload.parts[0].body.data;
console.log('error');
data = message_raw;
buff = new Buffer.from(data, 'base64');
text = buff.toString();
console.log(text);
}
});
});
}
Although this works perfectly fine, I am looking to find the 3 latest emails (not just 1) based on the email's "TO:" recipient which I currently have stored as the email variable.
Any help would be appreciated!
Have a look at here: https://developers.google.com/gmail/api/guides/filtering.
You can search or filter files using the messages.list and
threads.list methods. These methods accept the q parameter which
supports the same advanced search syntax as the Gmail web-interface.
Use filters via q property:
const email = 'myEmail#example.com';
gmail.users.messages.list({
auth: auth,
userId: 'me',
maxResults: 1,
q: `to:${email}`
}
Beside to:address you can use all other search/filter params, like is:unread etc.
I am making an application that searches for YouTube videos, and creates a playlist with those videos.
I've figured how to search for videos just using the API key provided, but am completely lost on how to do stuff as a user. At first, I thought that I could just use a service account to host all of the playlist, but I've learned quickly that YouTube doesn't support that.
My ultimate goal is to just create a playlist on my personal YouTube account, so I want that account to be hardcoded in.
I am struggling on changing it from using a service account to my personal account. Many other solutions I've seen use an express server with a frontend component, neither of which I have.
import { google } from 'googleapis';
import fs from 'fs';
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
const key = require('./credentials.json');
const scopes = "https://www.googleapis.com/auth/youtube";
const jwt = new google.auth.JWT(key.client_email, null, key.private_key, scopes);
jwt.authorize((err, response) => {
const yt = google.youtube({
version: 'v3',
auth: jwt,
});
yt.search.list({
part: 'snippet',
q: 'study music | zen music -livestream -live',
maxResults: 1,
}, (err, data) => {
if (err) console.error(err);
if (data) {
console.log(data.data.items);
// TODO: create playlist with these items
}
});
});
Currently, it's using a service account to search for videos. I haven't done the playlist creation part yet because it would just keep giving an error as I'm using a service account.
The YouTube API does not support service accounts. You will need to use Oauth2 and save the refresh token. You can then use your refresh token to request a new access token whenever you need.
I recommend following quickstart
var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
var OAuth2 = google.auth.OAuth2;
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'youtube-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 YouTube API.
authorize(JSON.parse(content), getChannel);
});
/**
* 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 oauth2Client = new 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), (err) => {
if (err) throw err;
console.log('Token stored to ' + TOKEN_PATH);
});
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 getChannel(auth) {
var service = google.youtube('v3');
service.channels.list({
auth: auth,
part: 'snippet,contentDetails,statistics',
forUsername: 'GoogleDevelopers'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var channels = response.data.items;
if (channels.length == 0) {
console.log('No channel found.');
} else {
console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
'it has %s views.',
channels[0].id,
channels[0].snippet.title,
channels[0].statistics.viewCount);
}
});
}
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);
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);
}