Related
When I make a call to ses.sendEmail the promise is never called. This is function in my lambda layer being called by my lambda function.
Here's the sendEmail function I have..
var aws = require ('aws-sdk')
var ses = new aws.SES ({region: 'us-west-2'});
exports.sendEmail = async (to, from, subject, body) => {
var params = {
Destination: {
ToAddresses : [to]
},
Message: {
Body: {
Html: {
Charset: "UTF-8",
Data : body
}
},
Subject: {
Charset: "UTF-8",
Data : subject
}
},
Source: from
}
console.log ("Sending email with params (stringify) : ", JSON.stringify(params));
console.log ("SES = ", ses);
await ses.sendEmail (params, function (err, data) {
console.log ("inside email");
});
}
I see the "Sending email with params (stringify)" log output and the "SES = " log output (which shows what appears to be a valid SES). But I never see "inside email" in the log nor do I receive any emails.
I'm also outside of the ses sandbox as I've gotten approval after setting up my domain and successfully verifying it all. I am on the us-west-2 region. I have not verified any email addresses. Just the domain (ie. name#user.host... user.host is verified) as I assumed if the domain is verified then any email from that domain should be good for use in the "from".
I have used async await instead of promises. I was facing the same issue. Email is dispatched successfully in my local computer but when I deploy the lambda and start testing it live. Emails were not sent to the email address mentioned.
async function sendMail(content, email) {
const params = {
Destination: {
ToAddresses: [email.toLowerCase()],
},
Message: {
Body: {
Text: {
Charset: "UTF-8",
Data: content.text,
},
Html: {
Charset: "UTF-8",
Data: content.html,
},
},
Subject: {
Charset: "UTF-8",
Data: content.subject,
},
},
Source: secretManagerData.senderEmail,
};
const sendPromise = await AWS_SES.sendEmail(params).promise();
console.log(sendPromise);
}
There are mistakes in calling the functions. When you call the function, you don't need to give a space with it.
var aws = require('aws-sdk'); // these are functions, call them without space
var ses = new aws.SES({region: 'us-west-2'}); // this
exports.sendEmail = async (to, from, subject, body) => {
try {
var params = {
Destination: {
ToAddresses : [to]
},
Message: {
Body: {
Html: {
Charset: "UTF-8",
Data : body
}
},
Subject: {
Charset: "UTF-8",
Data : subject
}
},
Source: from
}
console.log("Sending email with params (stringify) : ", JSON.stringify(params));
console.log("SES = ", ses);
const data = await sendmail(params);
return data;
} catch (e) {
console.log(e);
}
}
const sendmail = (params) => {
return new Promise((res, rej) => {
sendEmail(params = {}, (err, data)=>{
if (err) rej(err);
else res(data);
})
})
}
When you have to use async/await, you don't need to use callback and always use try/catch with async/await.
I got somehow same issue, it was resolved after used await before invoke sendEmail with promise and added API-Version for SES object.
const AWS = require ('aws-sdk')
const SES = new AWS.SES({apiVersion: '2010-12-01'});
try{
await SES.sendEmail(params).promise();
console.log("Email sent successfully");
}catch(err){
console.log("Failed to send email");
}
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);
}
I have a contactform created with Nodemailer. Now I want a Jade tempate mail being send whenever the customer submits the contactform.
I already got it working and the mail template is already being send, but somehow the content of the Jade file is being presented in the 'subject' header of the mail. And everyting is presented with all the HTML tags. So, somewhere it goes wrong.
This is my Nodemailer code:
router.post('/contact/send', function(req, res) {
var transporter = nodeMailer.createTransport({
service : 'Gmail',
auth : {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASS
}
});
var mailOptions = {
from: req.body.name + ' <' + req.body.email + '>',
to: 'xxxxx#gmail.com',
subject:'Website verzoek',
text:'Er is een website verzoek binnengekomen van '+ req.body.name+' Email: '+req.body.email+'Soort website: '+req.body.website+'Message: '+req.body.message,
html:'<p>Websiteverzoek van: </p><ul><li>Naam: '+req.body.name+' </li><li>Email: '+req.body.email+' </li><li>Soort website: '+req.body.website+' </li><li>Message: '+req.body.message+' </li></ul>'
};
transporter.sendMail(mailOptions, function (err, info) {
if(err) {
console.log(err);
res.redirect('/#contact');
} else {
console.log('Message send');
res.redirect('/#contact');
}
});
var toAddress = req.body.email;
var sendMail = function(toAddress, subject, content, next) {
var mailTemplate = {
from: 'xxxxxx#gmail.com',
to: toAddress,
subject: subject,
html: content
};
transporter.sendMail(mailTemplate, next);
};
var template = process.cwd() + '/views/mails/mail.jade';
fs.readFile(template, 'utf8', function(err, file) {
if (err) {
console.log('Error');
} else {
var compiledTmpl = jade.compile(file, {filename: template});
var context = {title: 'Express'};
var html = compiledTmpl(context);
sendMail(toAddress, html, function(err, response) {
if(err) {
console.log('ERROR!');
} else {
console.log('Template send');
}
});
}
});
});
The problem is a typo mistake. Your sendMail function takes subject as second paramter.
var sendMail = function(toAddress, subject, content, next) {
var mailTemplate = {
from: 'xxxxxx#gmail.com',
to: toAddress,
subject: subject,
html: content
};
transporter.sendMail(mailTemplate, next);
};
Your are passing the compiled html as a second parameter to the function. So it takes the html as header.
sendMail(toAddress, html, function(err, response) {
if(err) {
console.log('ERROR!');
} else {
console.log('Template send');
}
});
Cheers.
How can I delete a call recording from twilio using the npm module ?
I am trying to use the request module like this:
request.del(recording_url, {
'auth': {
'user': accountSid,
'pass': authToken
}
}, function (err, done){
if(err){
console.log("error deleting from twilio", err)
} else {
console.log("removed from twilio", done);
}
});
I am getting the following info in the done object:
body: '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<TwilioResponse><RestException><Code>20004</Code><Message>Method not allowed</Message><MoreInfo>https://www.twilio.com/docs/errors/20004</MoreInfo><Status>405</Status></RestException></TwilioResponse>'
Twilio evangelist here.
If you are using the Twilio node library you use this code to delete a recording resource:
var accountSid = 'AC3137d76457814a5eabf7de62f346d39a';
var authToken = "{{ auth_token }}";
var client = require('twilio')(accountSid, authToken);
client.recordings("RE557ce644e5ab84fa21cc21112e22c485").delete(function(err, data) {
if (err) {
console.log(err.status);
throw err.message;
} else {
console.log("Sid RE557ce644e5ab84fa21cc21112e22c485 deleted successfully.");
}
});
Check out the Recordings resource documentation for more info, specifically this example on deleting recordings.
Hope that helps.