AWS lambda function failing in Amazon Connect - aws-lambda

I have written an AWS lambda function in node.js to send an email, which is invoked in an Amazon Connect contact flow. In the error branch it plays a prompt saying "lambda function failed." I verified the IAM role has permission to send email with SES, the sender/receiver emails are verified in SES, and also the lambda function has permissions for Amazon Connect.
The email does actually get sent out, but oddly I still hear the prompt "lambda function failed." Here is the code:
"use strict";
const aws = require('aws-sdk');
const sender = "Sender Name <sender#email.com>";
const recipient = "recipient#email.com";
const subject = "ALERT: no agents are logged in";
const body_text = "There are no agents logged in";
const body_html =
`<html>
<head></head>
<body>
<h1>ALERT</h1>
<p>Ther are no agents logged in to take calls in the queue.</p>
</body>
</html>`;
const charset = "UTF-8";
let params = {
Source: sender,
Destination: {
ToAddresses: [
recipient
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
},
};
const ses = new aws.SES({apiVersion: '2010-12-01'});
exports.handler = function(event, context, callback) {
ses.sendEmail(params, function(err, data) {
if(err) {
console.log("fail");
callback(err, err.message);
}
else {
console.log("success");
callback(null);
}
});
};
I checked the cloudwatch logs and don't see any error:
00:17:24
START RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91 Version: $LATEST
00:17:24
2018-08-06T00:17:24.723Z 17f1e239-990e-11e8-96bb-a1980f44db91 success
00:17:24
END RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91
00:17:24
REPORT RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91 Duration: 226.51 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 32 MB
How do I troubleshoot this?
EDIT:
I enabled contact flow logs. In CloudWatch I noticed this:
{
"Parameters": { "FunctionArn": "arn:aws:lambda:us-west-2:769182588423:function:noAgentEmail", "TimeLimit": "8000" },
"Timestamp": "2018-08-06T06:36:31.786Z",
"ContactFlowModuleType": "InvokeExternalResource",
"Results": "The Lambda Function Returned An Error.",
"ExternalResults": { "forceClose": "false" },
"ContactId": "458027b0-d895-439e-bc06-114500dce64a",
"ContactFlowId": "arn:aws:connect:us-west-2:769182588423:instance/1e2ddedd-8335-42fe-89de-1e986fc016ef/contact-flow/2329af39-682c-4dc8-b3a2-5e7fe64de5d2"
}
What's confusing is it indicates the lambda function returned something:
"ExternalResults": { "forceClose": "false" }
But this is clearly not the case given the code. What's going on?

There's a couple of things to ensure your working Lambda function can be used for Connect.
As mentioned in the above answer, the return value should be a flat JSON object. As the guide says:
Nested and complex objects are not supported.
So, it should look something like this when you execute the lambda directly:
Response:
{
"statusPop": "success",
"id": "1",
"name": "me",
}
The property names and values need to be
alphanumeric, dash, and underscore characters.
Oh, and here's what tripped me up:
Amazon Connect sends paramters as part of a hierarchical structure, so any parameters you're expecting need to be referenced as
event['Details']['Parameters']['statusPop'];
instead of
event['statusPop'];
And don't forget to add permissions using:
aws lambda add-permission --function-name function:my-lambda-function --statement-id 1 \
--principal connect.amazonaws.com --action lambda:InvokeFunction --source-account 123456789012 \
--source-arn arn:aws:connect:us-east-1:123456789012:instance/def1a4fc-ac9d-11e6-b582-06a0be38cccf
More info and in depth details of what I mentioned here: https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html

Your lambda needs to return a flat filed JSON object. It looks like on success your lambda callbacks with null.
Update your exports to this:
exports.handler = function(event, context, callback) {
ses.sendEmail(params, function(err, data) {
if(err) {
console.log("fail");
//callback(err, err.message);
callback(null, {"status":"error"});
}
else {
console.log("success");
callback(null, {"status":"success"});
}
});
});
More information on lambda/connect requirements here: https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html
Note:
The output returned from the function must be a flat object of key/value pairs, with values that include only alphanumeric, dash, and underscore characters. Nested and complex objects are not supported. The size of the returned data must be less than 32 Kb of UTF-8 data.

you must return a valid JSON object such as below example
export const handler = async(event) => {
console.log("event" + JSON.stringify(event));
let amount = event['Details']['Parameters']['amount'];
console.log("amount:" + amount);
let resultMap = {
"amountValid": "valid"
};
console.log("resultMap" + JSON.stringify(resultMap));
return resultMap;
};

Related

SQS send message error using #aws-sdk node js at AWS lambda

I am trying to send message from my AWS Lambda to AWS SQS but it isn't quiet working and throwing me the error.
2022-12-26T14:58:31.651Z 282ada00-ea4a-45b6-afe4-e3a7f16e8c5a INFO MissingParameter: The request must contain the parameter Label.
at throwDefaultError (/var/task/node_modules/#aws-sdk/smithy-client/dist-cjs/default-error-handler.js:8:22)
at deserializeAws_queryAddPermissionCommandError (/var/task/node_modules/#aws-sdk/client-sqs/dist-cjs/protocols/Aws_query.js:292:51)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async /var/task/node_modules/#aws-sdk/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24
at async /var/task/node_modules/#aws-sdk/middleware-signing/dist-cjs/middleware.js:14:20
at async /var/task/node_modules/#aws-sdk/middleware-retry/dist-cjs/retryMiddleware.js:27:46
at async /var/task/node_modules/#aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:5:22
at async sendToSQS (/var/task/sendToSqs.js:28:20)
at async exports.handler (/var/task/index.js:19:22) {
'$fault': 'client',
'$metadata': {
httpStatusCode: 400,
requestId: 'bf137e9a-24bc-52bd-9416-22b99c6b82f5',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
Type: 'Sender',
Code: 'MissingParameter',
Detail: ''
}
I am not sure what parameters and in which way I need to include to make it work.
This is my code for sending message, where from my main module I send a simple data value as part of my message to be sent to SQS.
const { SQSClient, AddPermissionCommand } = require("#aws-sdk/client-sqs");
const sendToSQS=async(data)=>{
const client = new SQSClient({ region: "eu-west-1" });
var params = {
DelaySeconds: 0,
MessageAttributes: {
"Author": {
DataType: "String",
StringValue: "event params"
},
},
MessageGroupId:`${data}`,
MessageBody:JSON.stringify(data),
QueueUrl:"https://sqs.eu-west-1.amazonaws.com/000011110000/Salesforce-cqd-orders-delayer-retry"
};
try{
const command = new AddPermissionCommand(params);
let queueRes = await client.send(command);
console.info("[LAMBDA/#sqs] retry mechanism has succeeded. Data sent to SQS successfully")
console.log(queueRes)
const response = {
statusCode: 200,
body: "Data sent from lambda to sqs successfully.",
};
return response
}catch(error){
console.error("[LAMBDA/#s] retry mechanism has failed. Data wasn't sent to SQS")
console.log(error)
const response = {
statusCode: 200,
body: "Lambda to SQS error",
};
return response;
}
}
module.exports={sendToSQS}
Your message has delaySeconds which is not required and MessageGroupId which is only required for FIFO queue.
You can check sendMessage code reference from here AWS Wiki
Also, check this API reference for Send Message

NodeJS: AWS SDK V3: Not receiving any response data from lambda function

I'm trying to use the v3 javascript sdk to invoke a AWS Lambda function, and I'm having problems getting any meaningful response.
My code looks like so...
const { Lambda } = require("#aws-sdk/client-lambda");
const client = new Lambda();
const params = {
FunctionName: "MyLamdaFuncton",
Payload: JSON.stringify({ "action": "do_something" }),
InvocationType: "Event"
};
client.invoke(params)
.then((response) => {
console.log(JSON.stringify(response,null,4));
})
.catch((err) => {
console.error(err);
})
I can confirm from checking the CloudWatch logs that the lambda function works as exepcted. However this is the response I get in my NodeJS code...
{
"$metadata": {
"httpStatusCode": 202,
"requestId": "d6ba189d-9156-4f01-bd51-efe34a66fe34",
"attempts": 1,
"totalRetryDelay": 0
},
"Payload": {}
}
How do I get the actual response and status from the Lambda function?
If I change the payload above to intentionally throw an exception in my Lambda, the response in the console is still exactly the same.
update:
The Lambda function is written in Ruby. The response is returned like so...
{ statusCode: 200, body: JSON.generate(response.success?) }
where "response" is from another service it calls internally.
I've figured out what I was doing wrong. The issue was the "InvocationType". I got it working by changing to...
InvocationType: "RequestResponse"
Then I had to extract the response data like so...
const response_data = JSON.parse(new TextDecoder("utf-8").decode(response.Payload))

How to call one lambda from another in AWS SAM

I'm writing application with multiple functions inside SAM application. I can invoke lambda function that is already deployed to AWS with code similar to AWS Lambda call Lambda but it doens't work with local functions. I tried things from https://github.com/awslabs/aws-sam-cli/issues/510 but nothing seem to work yet.
This is closest I got yet (you need sam local start-lambda --host 172.17.0.1 where host is in docker network)
var AWS = require("aws-sdk");
exports.lambdaHandler = async (event, context) => {
let lambda = new AWS.Lambda({});
if (process.env.AWS_SAM_LOCAL) {
var ep = new AWS.Endpoint("http://172.17.0.1:3001");
lambda = new AWS.Lambda({ endpoint: ep });
}
const body = await new Promise(r => {
lambda.invokeAsync(
{
FunctionName: "myFunction",
InvokeArgs: JSON.stringify({ arguments: "for other function" })
},
function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
r(data);
}
);
});
response = {
statusCode: 200,
body: JSON.stringify(body)
};
return response;
};
and at least I see some activity, but I get error in invokeAsync
PathNotFoundLocally: PathNotFoundException\n at Object.extractError (/var/task/node_modules/aws-sdk/lib/protocol/json.js:51:27)\n
and this error in start-lambda console
2019-12-20 16:07:02 172.17.0.7 - - [20/Dec/2019 16:07:02] "POST /2014-11-13/functions/myFunction/invoke-async/ HTTP/1.1" 404 -
UPDATE:
I was able to fix ssl error that I had in first version of this question, and it looks like it works in sync mode with lambda.invoke
It looks like this is not currently supported until https://github.com/awslabs/aws-sam-cli/pull/749 is merged

CloudWatch log multiple custom metric filters to trigger lambda function

I just start learning on aws, and I have created couple of cloudwatch log custom metric filter and subscribe to the same lambda function do to some stuff, but I want to get my lambda function to perform difference action depends on which metric filter trigger it. In the lambda function(in python) how can I know which metric filter trigger it?
In Lambda function handlers, you'll have an event object, this object will usually contain information about the request. According to the documentation, this is base64 encoded and zipped. I imagine this is because the logs are expected to get fairly large.
Once unzipped the structure is:
{ messageType: 'DATA_MESSAGE',
owner: '123456789123',
logGroup: 'testLogGroup',
logStream: 'testLogStream',
subscriptionFilters: [ 'testFilter' ],
logEvents:
[ { id: 'eventId1',
timestamp: 1440442987000,
message: '[ERROR] First test message' },
{ id: 'eventId2',
timestamp: 1440442987001,
message: '[ERROR] Second test message' } ] }
This can be found in the AWS docs under CloudWatch Logs.
You can check on the subscriptionFilters field in the event data to check which filter triggered the Lambda.
If your Lambda was in NodeJS, you could write something like the following:
const zlib = require('zlib');
const YOUR_FILTER = 'filter1';
exports.handler = async (event) => {
const zippedInput = new Buffer(event.awslogs.data, 'base64');
await new Promise((resolve, reject) => {
zlib.gunzip(zippedInput, function (e, buffer) {
const awslogsData = JSON.parse(buffer.toString('utf-8'));
// Conditions on filters to do something
if (awslogsData.subscriptionFilters.includes(YOUR_FILTER)) {
// do something
} else if (awslogsData.subscriptionFilters.includes('your_other_filter')) {
// do something else
}
resolve();
});
})
return 'Hello from Lambda!'
};

Amazon Connect - Invoke Lambda

get_customer_input
ReasonForCalling_Play_prompt
Lambda function returns properly when testing Lex chatbot.
Invoking the function through Amazon Connect results in an Error.
Any ideas on how to save the return from Lambda in Connect with proper formatting? * Updated to add updated lambda code and Lex configuration image.
Lambda Code:
console.log('Loading event');
var AWS = require('aws-sdk');
var db = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
exports.handler = (event, context, callback) => {
var ssn = event.currentIntent.slots.userSSN;
var mySecret = event.currentIntent.slots.secretWord;
var params = {
TableName: 'users',
Key: {
"fourDigSSN": {
"N": ssn
},
"mySecretWord": {
"S": mySecret
}
},
AttributesToGet: ["accountBalance"]
};
db.getItem(params, function(err, data) {
if (err) {
console.log(" It didn't work and here is the error " + err); // an error occurred
}
else
callback(null, {
"sessionAttributes":{
"accountBal": data.Item.accountBalance.N
},
"dialogAction": {
"type": "ConfirmIntent",
"message": {
"contentType": "PlainText",
"content": "Your account has been verified. Your account balance is " + data.Item.accountBalance.N + "."
},
"intentName": "ReasonForCalling",
"slots": {
"userSSN": ssn,
"secretWord": mySecret
}
}
});
// var accountBal = data.Item.accountBalance.N;
// //console.log("GetDBItem succeeded:", JSON.stringify(data, null, 2));
// callback(null, {accountBalance : accountBal});
});
};
[lex_configuration][1]
[connect_contact_flow][2]
[connect_lambda_Details][3]
Your Amazon Connect contact flow appears to be referencing $.External.(something) after matching the "ReasonForCalling" intent. $.External is only used if Amazon Connect is calling upon a Lambda function directly. In your case, Amazon Connect is interacting with Lex. Lex is calling Lambda, so any response you get back to Amazon Connect will be coming from Lex, not Lambda. In order for Amazon Connect to read back something from Lex, you will need to use one of the following in your Play prompt step:
$.Lex.IntentName
$.Lex.Slots.(slotName)
$.Lex.SessionAttributes.(sessionAttributeKey)
Option1: You can configure Lex to insert the Account Balance into a slot called "AccountBal", then "return parameters to client". From there, Amazon Connect can access that value as $.Lex.Slots.AccountBal.
Option2: If you want Amazon Connect to interact directly to Lambda, you can use Lex to collect userSSN and secretWord and then build a separate Lambda function that Amazon Connect calls directly using an "Invoke AWS Lambda Function" step to perform the database lookup. With this method, you would be receiving a response directly from Lambda and can reference it as $.External.accountBalance.
EDITED ----------
Your callback has DialogAction type set as "ConfirmIntent" so Lex is most likely still expecting some user response. I doubt Amazon Connect is even getting anything back from Lex. Try updating the callback to something like the following so Lex will finish fulfillment and return to Amazon Connect:
callback(null, {
"sessionAttributes":{
"accountBal": data.Item.accountBalance.N
},
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "Your account has been verified. Your account balance is " + data.Item.accountBalance.N + "."
}
}
});

Resources