I have a pretty basic code snippet to test publishing of messages to SNS from Node Lambda:
exports.handler = async () => {
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-2'});
let result;
try {
result = await new AWS.SNS({apiVersion: '2010-03-31'}).publish({
TopicArn: 'arn:aws:sns:us-east-2:99999999999:MyTopic',
Message: 'Body of Message 1',
Subject: 'Message 1'
});
} catch (err) {
console.error('xxxxxxxx', err, err.stack);
throw err;
}
console.info('>>>>>> ' + result.MessageId);
}
However, all I repeatedly get in the logs is >>>>>> undefined, and of course, the messages are not being published (because the queue subscribed to this is always empty). I can confirm that the Lambda function has the relevant permissions. What am I doing wrong?
You are not converting publish to a promise. This means that result = await will not work as expected.
Read my old answer for more understanding: How to use Async and Await with AWS SDK Javascript
TLDR;
result = await new AWS.SNS({apiVersion: '2010-03-31'}).publish({
TopicArn: 'arn:aws:sns:us-east-2:99999999999:MyTopic',
Message: 'Body of Message 1',
Subject: 'Message 1'
}).promise(); // !!!
Related
I am working on a Serverless AWS service that uses Twilio's programmable SMS to deliver text messages.
My setup is consistently delivering messages successfully when I run the stack locally (e.g. sls offline start), but in the deployed environment I seem to be unable to even call the method on the Twilio client.
Here's how the message delivery is set up:
const twilio = require('twilio');
const twilioClient = twilio(
process.env.TWILIO_SID,
process.env.TWILIO_TOKEN,
{
lazyLoading: true,
}
);
export function sendMessage(user, message) {
twilioClient.messages.create({
from: process.env.TWILIO_NUMBER,
to: user.phone,
body: message,
}, function(err, message) {
console.log('error', err);
console.log('message', message);
});
}
// And then usage in a Serverless Function Handler
function example(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
// user is also determined here
sendMessage(user, 'This is a message');
return {
body: JSON.stringify({}),
statusCode: 200
};
}
Locally, running this works and I am able to see the output of the message log, with nothing on the error log. However, when deployed, running this yields nothing -- the method appears to not even get called (and I can verify in the Twilio logs that no API call was made), therefor no error or message logs are made in the callback.
In debugging I've tried the following:
I've logged all the environment variables (Twilio SSID, auth token, phone number), as well as the function arguments, and they all appear to be in place. I also checked out the Lambda function itself to make sure the environment variables exist.
I've examined my CloudWatch logs; no errors or exceptions are logged. Other than the Twilio method not getting called the Lambda function is executed without issue.
I've tried logging things like twilio and twilioClient.messages.create to make sure the client and function definition didn't get wiped out somehow.
I thought maybe it had to do with context.callbackWaitsForEmptyEventLoop so I changing it from false to true.
I have come up empty, I can't figure out why this would be working locally, but not when deployed.
Edit: per the Twilio client example, if you omit the callback function the method will return a Promise. I went ahead and tried to await the response of the method:
export function sendMessage(user, message) {
return twilioClient.messages.create({
from: process.env.TWILIO_NUMBER!,
to: user.phone,
body: message,
});
}
// Usage...
async function example(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
try {
const message = await sendMessage(user, 'This is a message');
console.log('message', message)
} catch (error) {
console.log('error', error);
}
return {
body: JSON.stringify({}),
statusCode: 200
};
}
In this example the Lambda function is successful, but neither the message nor the error are logged.
I tried this and it works. I tried to make my code as similar to use, with a few changes.
const twilio = require('twilio');
const twilioClient = twilio(
process.env.TWILIO_SID,
process.env.TWILIO_TOKEN
);
let user = '+14075551212';
function sendMessage(user, message) {
return twilioClient.messages.create({
from: process.env.TWILIO_NUMBER,
to: user,
body: message,
});
}
exports.handler = async function(event, context, callback) {
try {
const message = await sendMessage(user, 'This is a message');
console.log('message', message);
callback(null, {result: 'success'});
} catch (error) {
console.log('error', error);
callback("error");
}
};
My AWS Lambda function sends an email through SNS, but the response is null in the Lambda console. I used a blueprint provided by AWS and put the code in a Lambda handler using Node.js 10.x.
https://github.com/awsdocs/aws-doc-sdk-examples/blob/master/javascript/example_code/sns/sns_publishtotopic.js
I am not that experienced in the use of promises in node.js and my research on Stack Overflow seems to indicate that could be the problem here.
'use strict';
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'us-west-2'});
exports.handler = (event, context, callback) => {
// Create publish parameters
var params = {
//Message: `The fluid level is low on your Knight system: ${event.my_volume} mL.`,
Message: `The fluid level is low on your Knight system.`,
Subject: `Low Fluid Level Alert - Knight`,
TopicArn: `arn:aws:sns:us-west-2:468820349153:MyLowVolumeTopic`,
};
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();
// Handle promise's fulfilled/rejected states
publishTextPromise.then(
function(data) {
console.log("Message: " + params.Message);
console.log("Sent to the topic: " + params.TopicArn);
console.log("MessageID is " + data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});
};
The result is that I receive the email message inconsistently and see a null response in the Lambda console. Here is a sample log result:
Response: null
Request ID: "9dcc8395-5e17-413a-afad-a86b9e04fb97"
Function Logs:
START RequestId: 9dcc8395-5e17-413a-afad-a86b9e04fb97 Version: $LATEST
2019-08-17T21:44:31.136Z 9dcc8395-5e17-413a-afad-a86b9e04fb97 Message: The fluid level is low on your Knight system.
2019-08-17T21:44:31.136Z 9dcc8395-5e17-413a-afad-a86b9e04fb97 Sent to the topic: arn:aws:sns:us-west-2:468820349153:MyLowVolumeTopic
2019-08-17T21:44:31.136Z 9dcc8395-5e17-413a-afad-a86b9e04fb97 MessageID is cb139fb6-5d37-574e-9134-ca642a49fde5
END RequestId: 9dcc8395-5e17-413a-afad-a86b9e04fb97
REPORT RequestId: 9dcc8395-5e17-413a-afad-a86b9e04fb97 Duration: 1367.98 ms Billed Duration: 1400 ms Memory Size: 128 MB Max Memory Used: 75 MB
Your lambda is exiting before completing the promise. To circumvent this you can use async-await:
exports.handler = async (event, context, callback) => {
const data = await publishTextPromise;
//maybe some console log here
return data;
}
OR, You can return the promise, return publishTextPromise.then(...).catch(...)
My transporter is set-up as such:
const ses = new aws.SES();
var transporter = mailer.createTransport({
SES: ses,
sendingRate: 25
});
I have a sendEmail function that sets up an email to send an attachment:
function sendEmail(body, filename, customer_name) {
var mailOptions = {
from: "test#test.com",
subject: "A test subject - " + customer_name,
html: '<p>' + customer_name + '</p><br />Please see attached file.',
to: "testing#testing.com",
attachments: [
{
filename: filename + ".txt",
content: body.toString()
}
]
};
return transporter.sendMail(mailOptions);
};
Within the Lambda, I add the sendEmail to an array of promises and process as such:
module.exports.publish = async (event, context, callback) => {
...
var promises = data.map(async (i) => {
...
await sendEmail(data, file_prefix, customer_name);
};
await Promise.all(promises).then(() => {
addMessageToLog('Emails sent successfully');
});
await sendSNS(logger).then(() => {
if (isError) { callback(errorObj); }
callback(null, 'Emails successfully sent.');
});
My issue is that when I invoke this locally using the Serverless framework in VS Code, all my emails run perfectly fine at what seems like a 25/sec rate as specified. When I deploy to AWS Lambda, it simply sends 25 emails, then the request ends. Locally, it sends my SNS topic my log messages, but not on the Lambda. It abruptly ends at 25 every time.
Am I using the sendingRate correctly in this case? Why would the AWS Lambda end the request but not my test locally?
I'll put this here for posterity in case anyone else runs into this...
I set the maxConnections to 1 and this fixed my issue on AWS.
so on my first time learning AWS stuff (it is a beast), I'm trying to create e-mail templates, I have this lambda function:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({ region: "us-east-1" });
exports.handler = async (event, context, callback) => {
// Create createTemplate params
var params = {
Template: {
TemplateName: "notification" /* required */,
HtmlPart: "HTML_CONTENT",
SubjectPart: "SUBJECT_LINE",
TextPart: "sending emails with aws lambda"
}
};
// Create the promise and SES service object
const templatePromise = new AWS.SES({ apiVersion: "2010-12-01" })
.createTemplate(params)
.promise();
// Handle promise's fulfilled/rejected states
templatePromise
.then((data) => {
console.log(data);
callback(null, JSON.stringify(data) );
// also tried callback(null, data);
}, (err) => {
console.error(err, err.stack);
callback(JSON.stringify(err) );
});
as far as I am understanding, this function should return me a template? an object, anything? when I use the lambda test functionality I always got null in the request response
does anyone know what I am doing wrong here?
edit: and It is not creating the e-mail template, I check the SES Panel - email templates and it is empty
edit2: if I try to return a string eg: callback(null, "some success message"); it does return the string, so my guess is something wrong with the SES, but this function is exactly what we have in the AWS docs, so I assume it should just work..
Try not to resolve the Promise and change your code to just returning it as-is:
return await templatePromise;
which should present you some more detail of what is really going wrong in your code - it might be some hidden access issue - so you might need to adjust the role your lambda function is using. createTemplate on the other side should not return much in case of successful execution but just create the template.
Also try to follow the following try/catch pattern when using async (as described here in more detail: https://aws.amazon.com/de/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)
exports.handler = async (event) => {
try {
data = await lambda.getAccountSettings().promise();
}
catch (err) {
console.log(err);
return err;
}
return data;
};
Amazon Simple Email Service (Amazon SES)
I have the below code. It works perfectly if I use it from an aws ec2 instance or from my workstation. But as soon as I add it to a lambda function I am working on inside of an AWS VPC, the callback to my ses.sendEmail() never gets called. I never see a "sendEmail Function Error", or a "sendEmail Function Success" console.log() in my CloudWatch Logs for the function and my lambda function times out at the end of my timeout period. I don't know what more I can do.
I have looked for any IAM roles or policies that I might have to add, can't find any mention of them needed, or which ones to add.
Tried adding the Policy 'AmazonSESFullAccess' to my IAM role for the function. Still Times out.
let aws = require('aws-sdk')
, ses = new aws.SES({ apiVersion: '2010-12-01', region: 'us-west-2' })
;
sendEmail({
To : [ 'anEmail#someone.com' , 'anotherEmail#somewhereElse.com'],
From: 'ourSupportEmail#whereIWork.com',
Subject: 'Sending An Email Out',
Body: `<html> A Buch of HTML Here</html>`
}, function(err, result){
if(err){
console.error('SendEmail Error', err);
} else {
console.log('SendEmail Result', result);
}
});
function sendEmail(emailObj, cb){
emailObj.To.push('myEmail#whereIWork.com');
let mailData = {
Source: emailObj.From,
Destination: { ToAddresses: emailObj.To },
Message: {
Subject: {
Data: emailObj.Subject
},
Body: {
Html: {
Data: emailObj.Body
}
}
}
}
console.log('sending Email', JSON.stringify(mailData)); //see in Cloudwatch Logs! YEAH!!!
ses.sendEmail(mailData, function(err, data){
if(err){
console.log('sendEmail Function Error', JSON.stringify(err)); // never see in cloudwatch logs, WHAT??
cb(err);
} else {
console.log('sendEmail Function Success', JSON.stringify(data)); // never see in cloudwatch logs, WHAT??
cb(null, data);
}
});
}
You're lambda is set up inside a VPC, which in that case - has no access to the internet, thus no access to AWS APIs as well.
If you let your lambda run long enough - the callback will be called with a connection timeout error.
Please follow my detailed answer here.