I have followed gmail api for sending email. I am getting error as:
"message": "400 - \"{\n \\"error\\": {\n \\"errors\\": [\n {\n \\"domain\\": \\"global\\",\n \\"reason\\": \\"invalidArgument\\",\n \\"message\\": \\"'raw' RFC822 payload message string or uploading message via /upload/* URL required\\"\n }\n ],\n \\"code\\": 400,\n \\"message\\": \\"'raw' RFC822 payload message string or uploading message via /upload/* URL required\\"\n }\n}\n\""
Here is the piece of code I have written for sending mail using gmail api with node.js. Help me out to resolve the issue.
router.post('/composeMail', async (req, res, next) => {
function makeBody(to, from, subject, message) {
let str = ["Content-Type: text/plain; charset=\"UTF-8\"\n",
"Content-length: 5000\n",
"Content-Transfer-Encoding: message/rfc822\n",
"to: ", to,"\n",
"from: ", from,"\n",
"subject: ", subject,"\n\n",
message
].join('');
console.log("String: ", str);
// let encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
let encodedMail = btoa(str).replace(/\+/g, '-').replace(/\//g, '_');
return encodedMail;
}
let raw = makeBody("dinesh.kumar#gmail.com", "dinesh.kumar#gmail.com", "Test mail", "Everything is fine");
let obj = {};
obj.raw = raw;
let body = JSON.stringify(obj);
let option = {
url: "https://www.googleapis.com/gmail/v1/users/userId/messages/send",
method: 'POST',
headers: {
'Authorization': `Bearer ${req.query.access_token}`
},
qs: {
userId: 'me'
},
body: body
};
await request(option).then(body => {
return res.apiOk(body);
}).catch(err => {
return res.apiError(err);
})
});
You want to send an email using Gmail API by the request module.
If my understanding is correct, how about this modification? I think that there are several answers. So please think of this as one of them.
Modification points:
Please use https://www.googleapis.com/upload/gmail/v1/users/userId/messages/send as the endpoint.
Use the value as a string.
Add 'Content-Type': 'message/rfc822' to the headers.
Modified script:
Please modify makeBody() as follows.
function makeBody(to, from, subject, message) {
let str = [
"to: ", to, "\n",
"from: ", from, "\n",
"subject: ", subject, "\n\n",
message,
].join('');
return str;
}
Please modify option as follows.
let raw = makeBody("dinesh.kumar#gmail.com", "dinesh.kumar#gmail.com", "Test mail", "Everything is fine");
const userId = 'me'; // Please modify this for your situation.
let option = {
url: "https://www.googleapis.com/upload/gmail/v1/users/" + userId + "/messages/send",
method: 'POST',
headers: {
'Authorization': `Bearer ${req.query.access_token}`,
'Content-Type': 'message/rfc822',
},
body: raw,
};
Note:
This modified script supposes that Gmail API is enabled at API console and the required scope for sending emails is included in the scopes of access token.
Reference:
Users.messages: send
In my environment, I could confirm that this modified script worked fine. But if this was not what you want, I'm sorry.
Related
I'm trying to send an sms without the twilio sdk by using the nodejs https module however the twilio post api keeps responding with this error "400, Bad Request", which means I'm probably not crafting the request the right way. I've followed the nodejs docs https example, and also twilio's. I've also tried making curl post request and it works perfectly fine. Where I'm I getting it wrong. Here's my code
// Send an SMS message via Twilio
helpers.sendTwilioSms = (phone, message, callback) => {
// validate parameters
phone =
typeof phone == "string" && phone.trim().length == 10
? phone.trim().length
: false;
message =
typeof message == "string" &&
message.trim().length > 0 &&
message.trim().length <= 1600
? message.trim()
: false;
if (phone && message) {
// Configure the request payload
const payload = {
from: config.twilio.fromPhone,
to: `+234${phone}`,
body: message
};
// stringify payload using querystring module instead of JSON.stringify because the reqeust we'll be sending is not of application/json but 'application/x-www-form-urlencoded' form content-type as specified by Twilio
const stringPayload = querystring.stringify(payload);
// Configure the request details
var requestDetails = {
hostname: "api.twilio.com",
method: "POST",
path: `/2010-04-01/Accounts/${config.twilio.accountSid}/Messages.json`,
auth: `${config.twilio.accountSid}:${config.twilio.authToken}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(stringPayload)
}
};
// Instantiate the request
const req = https.request(requestDetails, res => {
// grab the status of the sent request
const status = res.statusCode;
console.log([
`(sendTwilioSms) making https post request`,
`(sendTwilioSms) response completed: ${res.complete}`,
`(sendTwilioSms) response statusCode: ${res.statusCode}`,
{ "(sendTwilioSms) response headers:": res.headers },
{ "(sendTwilioSms) response body:": res.body }
]);
// callback successfully if the request went through
if (status == 200 || status == 201) {
callback(false);
} else {
callback(500, {
Error: `Status code returned was ${status}: ${res.statusMessage}`
});
}
});
// Alert the user as to a change in their check status
workers.alertUserToStatusChange = newCheckData => {
const message = `Alert: Your check for ${newCheckData.method.toUpperCase()} ${
newCheckData.protocol
}://${newCheckData.url} is currently ${newCheckData.state}`;
helpers.sendTwilioSms(newCheckData.userPhone, message, err => {
if (!err) {
console.log(
"Success: User was aterted to a status change in their check, via sms: ",
msg
);
} else {
console.log(
"Error: Could not send sms alert to user who add a state change in their check"
);
}
});
Here's the Response:
[
'(workers) making check request',
'(workers) check response completed: false',
'(workers) check response statusCode: 200'
]
logging to file succeeded
Check outcome has not changed no alert needed
[
'(sendTwilioSms) making https post request',
'(sendTwilioSms) response completed: false',
'(sendTwilioSms) response statusCode: 400',
{
'(sendTwilioSms) response headers:': {
date: 'Fri, 17 Jan 2020 09:49:39 GMT',
'content-type': 'application/json',
'content-length': '127',
connection: 'close',
'twilio-request-id': 'RQ7ee0b52d100c4ac997222f235e760fb7',
'twilio-request-duration': '0.025',
'access-control-allow-origin': '*',
'access-control-allow-headers': 'Accept, Authorization, Content-Type, If-Match, '
+
'If-Modified-Since, If-None-Match, ' +
'If-Unmodified-Since',
'access-control-allow-methods': 'GET, POST, DELETE, OPTIONS',
'access-control-expose-headers': 'ETag',
'access-control-allow-credentials': 'true',
'x-powered-by': 'AT-5000',
'x-shenanigans': 'none',
'x-home-region': 'us1',
'x-api-domain': 'api.twilio.com',
'strict-transport-security': 'max-age=31536000'
}
},
{ '(sendTwilioSms) response body:': undefined }
]
Error: Could not send sms alert to user who add a state change in their check
Try with something like this:
// authentication
var authenticationHeader = "Basic "
+ new Buffer(config.twilio.accountSid
+ ":"
+ config.twilio.authToken).toString("base64");
// Configure the request details
var requestDetails = {
host: "api.twilio.com",
port: 443,
method: "POST",
path: `/2010-04-01/Accounts/${config.twilio.accountSid}/Messages.json`,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(stringPayload),
"Authorization": authenticationHeader
}
};
and this:
// Instantiate the request
const req = https.request(requestDetails, res => {
// grab the status of the sent request
const status = res.statusCode;
res.setEncoding('utf8');
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
console.log('Successfully processed HTTPS response');
// If we know it's JSON, parse it
if (res.headers['content-type'] === 'application/json') {
body = JSON.parse(body);
}
callback(null, body);
});
// callback successfully if the request went through
if (status == 200 || status == 201) {
callback(false);
} else {
callback(500, {
Error: `Status code returned was ${status}: ${res.statusMessage}`
});
}
});
I hope it works, I have not tested. If it doesn't let me know and I'll try on my side and post a complete tested code.
I am trying to send an email using POST request with just Node standard modules in NodeJS v8.10 via GMail API.
The sample code is given below.
I am getting an error:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "invalidArgument",
"message": "Recipient address required"
}
],
"code": 400,
"message": "Recipient address required"
}
}
It says recipient address required but according to what I think, (I may be wrong), my base64url conversion is proper since I checked it in Google API Explorer but my problem is in passing the data, I am doing it properly as told in the guide i.e. inside body with 'raw' key but it still does not work and this is where my problem must be. Maybe I am missing something, maybe I do not know the proper structure.
Yes, there are multiple posts regarding this but none of them provided solution.
I referred the guide on https://developers.google.com/gmail/api/v1/reference/users/messages/send
but the given example is of with the use of client library.
I tried everything, passing the 'raw' with base64url encoded data into write function of the request, passing it as a data parameter in options, passing it through body parameters in options, everything I can think of.
Am I missing something? Where am I going wrong?
I am a newbie in nodejs so please explain and if possible, an example structure of solution would be most welcome.
Base64url produced is working fine, I guess. I copied the string produced by conversion and tried it at https://developers.google.com/gmail/api/v1/reference/users/messages/send?apix=true
It works fine and sends me the mail but it does not work on my code.
var email = (
"Content-Type: text/plain; charset=\"UTF-8\"\n" +
"Content-length: 5000\n" +
"MIME-Version: 1.0\n" +
"Content-Transfer-Encoding: message/rfc2822\n" +
"to: something#something.com\n" +
"from: \"Some Name\" <something#gmail.com>\n" +
"subject: Hello world\n\n" +
"The actual message text goes here"
);
async function sendMail(token,resp) {
return new Promise((resolve,reject) => {
var base64EncodedEmail = Buffer.from(email).toString('base64');
var base64urlEncodedEmail = base64EncodedEmail.replace(/\+/g, '-').replace(/\//g, '_');
var params = {
userId: 'me',
resource: {
'raw': base64urlEncodedEmail
}
};
var body2 = {
"raw": base64urlEncodedEmail,
}
var options = {
hostname: 'www.googleapis.com',
path:'/upload/gmail/v1/users/me/messages/send',
headers: {
'Authorization':'Bearer '+token,
'Content-Type':'message/rfc822',
},
body: {
"raw": base64urlEncodedEmail,
'resource': {
'raw': base64urlEncodedEmail,
}
},
data: JSON.stringify({
'raw': base64urlEncodedEmail,
'resource': {
'raw': base64urlEncodedEmail,
}
}),
message: {
'raw': base64urlEncodedEmail,
},
payload: {
"raw": base64urlEncodedEmail, //this is me trying everything I can think of
},
// body: raw,
// }
userId: 'me',
// resource: {
// 'raw': base64urlEncodedEmail
// },
method: 'POST',
};
var id='';
console.log(base64urlEncodedEmail);
const req = https.request(options, (res) => {
var body = '';
res.on('data', (d) => {
body += d;
});
res.on('end', () => {
var parsed = body;
console.log(parsed);
})
});
req.on('error', (e) => {
console.error(e);
});
req.write(JSON.stringify(body2));
req.end();
});
};
Thank you for your time and answers.
I found the solution.
It says everywhere to convert the rfc822 formatted string to Base64url to send and attach it to 'raw' property in the POST body but I don't know what has changed and you don't need to do that anymore.
First things first, the Content-Type in header should be
'Content-Type':'message/rfc822'
Now, since we are specifying the content-type as message/rfc822, we don't need to convert the data we want to send into base64url format anymore, I guess (Not sure of the reason because I have a very little knowledge about this.)
Only passing "To: something#any.com" as body works.
Here is the complete code of how to get it done for someone who is struggling for the same problem.
function makeBody(to, from, subject, message) {
let str = [
"to: ", to, "\n",
"from: ", from, "\n",
"subject: ", subject, "\n\n",
message,
].join('');
return str;
}
async function getIdAsync(token,resp) {
return new Promise((resolve,reject) => {
let raw = makeBody("something#gmail.com", "something#gmail.com", "Subject Here", "blah blah blah");
var options = {
hostname: 'www.googleapis.com',
path:'/upload/gmail/v1/users/me/messages/send',
headers: {
'Authorization':'Bearer '+token,
'Content-Type':'message/rfc822'
},
method: 'POST',
};
const req = https.request(options, (res) => {
var body = '';
res.on('data', (d) => {
body += d;
});
res.on('end', () => {
var parsed = body;
console.log(parsed);
})
});
req.on('error', (e) => {
console.error(e);
});
req.write(raw);
req.end();
});
};
Happy Coding :)
I am trying to send mail using gmail api in my meteor application, which returning below error,
Error in calendar insert: Error: failed [400] { "error": { "errors": [ { "domain": "global", "reason": "invalidArgument", "message": "'raw' RFC822 payload message string or uploading message via /upload/* URL required" } ], "code": 400, "message": "'raw' RFC822 payload message string or uploading message via /upload/* URL required" } }
I have tried the below,
"sendGmail": function(str) {
this.unblock();
var url = "https://www.googleapis.com/gmail/v1/users/me/messages/send";
var encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
try {
Meteor.http.post(url, {
'headers' : {
'Authorization': "Bearer " + Meteor.user().services.google.accessToken,
'Content-Type': 'application/json'
},
'body': JSON.stringify({
"raw": encodedMail
})
});
} catch(e){
console.log("Error in calendar insert: " + e);
} finally {
return true;
}
}
passing the below string value as argument:
var str = "Content-Type: text/plain; charset=\"UTF-8\"\n" +
"MIME-Version: 1.0\n" +
"Content-Transfer-Encoding: 7bit\n" +
"to: arunmail2u#gmail.com\n" +
"from: arunsugan08#gmail.com\n" +
"subject: Meteor test mail\n\n" +
"Hi, this is test mail from meteor application";
Meteor.call('sendGmail', str);
A body string is given as content, not body. Check the documentation.
content - Takes a plain String and sets it on the HTTP request body.
I want to send an email through Google API without the unnecessary OAUTH2 parameters. I only have the access_token and the refresh_token of that user.
How can I send an email through Gmail API through a basic POST request in NodeJS, with Request npm plugin?
abraham is correct, but I just thought I'd give you an example.
var request = require('request');
server.listen(3000, function () {
console.log('%s listening at %s', server.name, server.url);
// Base64-encode the mail and make it URL-safe
// (replace all "+" with "-" and all "/" with "_")
var encodedMail = new Buffer(
"Content-Type: text/plain; charset=\"UTF-8\"\n" +
"MIME-Version: 1.0\n" +
"Content-Transfer-Encoding: 7bit\n" +
"to: reciever#gmail.com\n" +
"from: sender#gmail.com\n" +
"subject: Subject Text\n\n" +
"The actual message text goes here"
).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
request({
method: "POST",
uri: "https://www.googleapis.com/gmail/v1/users/me/messages/send",
headers: {
"Authorization": "Bearer 'access_token'",
"Content-Type": "application/json"
},
body: JSON.stringify({
"raw": encodedMail
})
},
function(err, response, body) {
if(err){
console.log(err); // Failure
} else {
console.log(body); // Success!
}
});
});
Don't forget to change the reciever and sender email address for the example to work.
There are two methods for attaching OAuth2 access_tokens to a Google API request.
Using the access_token query parameter like this: ?access_token=oauth2-token
Using the HTTP Authorization header like this: Authorization: Bearer oauth2-token
The second one is prefered for POST requests so the raw HTTP request for sending an email would look something like this.
POST /gmail/v1/users/me/messages/send HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer oauth2Token
{"raw":"encodedMessage"}
I'm trying to implement https://developers.podio.com/doc/items/add-new-item-22362 Podio API addItem call in a nodejs module. Here is the code:
var _makeRequest = function(type, url, params, cb) {
var headers = {};
if(_isAuthenticated) {
headers.Authorization = 'OAuth2 ' + _access_token ;
}
console.log(url,params);
_request({method: type, url: url, json: true, form: params, headers: headers},function (error, response, body) {
if(!error && response.statusCode == 200) {
cb.call(this,body);
} else {
console.log('Error occured while launching a request to Podio: ' + error + '; body: ' + JSON.stringify (body));
}
});
}
exports.addItem = function(app_id, field_values, cb) {
_makeRequest('POST', _baseUrl + "/item/app/" + app_id + '/',{fields: {'title': 'fgdsfgdsf'}},function(response) {
cb.call(this,response);
});
It returns the following error:
{"error_propagate":false,"error_parameters":{},"error_detail":null,"error_description":"No matching operation could be found. No body was given.","error":"not_found"}
Only "title" attribute is required in the app - I checked that in Podio GUI. I also tried to remove trailing slash from the url where I post to, then a similar error occurs, but with the URL not found message in the error description.
I'm going to setup a proxy to catch a raw request, but maybe someone just sees the error in the code?
Any help is appreciated.
Nevermind on this, I found a solution. The thing is that addItem call was my first "real"-API method implementation with JSON parameters in the body. The former calls were authentication and getApp which is GET and doesn't have any parameters.
The problem is that Podio supports POST key-value pairs for authentication, but doesn't support this for all the calls, and I was trying to utilize single _makeRequest() method for all the calls, both auth and real-API ones.
Looks like I need to implement one for auth and one for all API calls.
Anyway, if someone needs a working proof of concept for addItem call on node, here it is (assuming you've got an auth token beforehand)
_request({method: 'POST', url: "https://api.podio.com/item/app/" + app_id + '/', headers: headers, body: JSON.stringify({fields: {'title': 'gdfgdsfgds'}})},function(error, response, body) {
console.log(body);
});
You should set content-type to application/json
send the body as stringfied json.
const getHeaders = async () => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
};
const token = "YOUR APP TOKEN HERE";
headers.Authorization = `Bearer ${token}`;
return headers;
}
const createItem = async (data) => {
const uri = `https://api.podio.com/item/app/${APP_ID}/`;
const payload = {
fields: {
[data.FIELD_ID]: [data.FIELD_VALUE],
},
};
const response = await fetch(uri, {
method: 'POST',
headers: await getHeaders(),
body: JSON.stringify(payload),
});
const newItem = await response.json();
return newItem;
}