Call AES SES sendEmail from a Alexa skill - amazon-web-services

I have been trying for weeks now to figure out how to invoke a call to sendEmail from an Alexa-hosted node.js skill. It is a very simple skill where the user makes a selection. when the selection is made I want to send a email to myself with contents of the selection. I have been trying to call sendEmail from my index.js, which contains the logic for my skill. I created a IAM role with the proper .json file as indicated on AWS, and was able to run a basic node file from the aws command line interface that sends me a email. What kind of steps will I have to take to get my Alexa skill to send the email? Can I just invoke the lambda function that is already working from my Alexa skill?
I have been trying to put the code below, and code similar to it without nodemailer doing the basic ses send email. I started with the aws ses webpage. I cannot find a single tutorial that actually walks you through step by step of calling this ses send email function in a Alexa skill and I would be so grateful to be pointed in the right direction.
'''
const Alexa = require('ask-sdk-core');
const AWS = require("aws-sdk");
let nodemailer = require("nodemailer");
let aws = require("#aws-sdk/client-ses");
// configure AWS SDK
process.env.AWS_ACCESS_KEY_ID = "xxxx";
process.env.AWS_SECRET_ACCESS_KEY = "xxxx";
const ses = new aws.SES({
apiVersion: "2010-12-01",
region: "us-east-1",
});
// create Nodemailer SES transporter
let transporter = nodemailer.createTransport({
SES: { ses, aws },
});
// send some mail
transporter.sendMail(
{
from: "xxxx#gmail.com",
to: "xxxx#gmail.com",
subject: "Message",
text: "I hope this message gets sent!",
ses: {
// optional extra arguments for SendRawEmail
Tags: [
{
Name: "tag_name",
Value: "tag_value",
},
],
},
},
(err, info) => {
console.log(info.envelope);
console.log(info.messageId);
}
);
'''
edit: thanks for the responses already guys! The only notable error I am getting is there is a problem with the requested skills response. So my Alexa skill is amazon hosted. Do I have to change this to use SES,sendEmail()? In my lambda page on amazon I have a file in a folder called sendEmail() but that gives me a error about the line AWS = require(etc.. in the debugger with the output "errorMessage": "2021-03-14T00:33:00.315Z e19e74c6-4c00-47dc-9872-77c0a602541a Task timed out after 3.00 seconds" the code I have in the lambda function titled sendEmail is actually the below code. the line sendEmail() also gives there is a problem with the requested skills response from the Alexa. I do not see my Alexa skill in my lambda functions. Do I have to add it in? Sorry, I really am a noob for AWS programming. Thank you!
`AWS = require('aws-sdk');
// Set the region
AWS.config.update({region: 'us-east-1'});
// Create sendEmail params
var params = {
Destination: { /* required */
ToAddresses: [
'x#gmail.com',
/* more items */
]
},
Message: { /* required */
Body: { /* required */
Html: {
Charset: "UTF-8",
Data: "HTML_FORMAT_BODY"
},
Text: {
Charset: "UTF-8",
Data: "TEXT_FORMAT_BODY"
}
},
Subject: {
Charset: 'UTF-8',
Data: 'Test email'
}
},
Source: 'x#gmail.com', /* required */
ReplyToAddresses: [
'pamphl3t#gmail.com',
/* more items */
],
};
// Create the promise and SES service object
var sendPromise = new AWS.SES({apiVersion: '2010-12-01'}).sendEmail(params).promise();
// Handle promise's fulfilled/rejected states
sendPromise.then(
function(data) {
console.log(data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});`

I believe this part of the documentation is what you're looking for. after you put your custom skill on Lambda function you need to send the content of the body to your email:
const querystring = require('querystring');
let post_data = null;
exports.handler = function (event, context) {
post_data = querystring.stringify(event.body);
}
// your emailing code here
Check this repo too

Where are you defining process.env? If you're running in Alexa Hosted, you'll get environment variables for the Alexa-owned IAM role which will have no access to SES.
Have you tried hardcoding the key and secret from your personal AWS account? If that solves it, look at adding the dotenv package in your package.json, and follow its documentation to set the values in a .env file you can add to your .gitignore.

const AWS = require( 'aws-sdk' );
var SES = new AWS.SES( { apiVersion: '2010-12-01', region: 'us-east-1' } );
//when we are sending our email
//we have to return a promise
//this is because the sendEmail funx
//from SES
//is an ASYNCHRONOUS CALL to the simple email server
//we have to wait for this call to complete before RET
//this is the vital line that resolves my issue
if (typeof Promise === 'undefined' ) {
AWS.config.setPromisesDependency( require( 'bluebird' ) );
}
sendEmail("SOME TEXT");
function sendEmail ( text,event, context, callback ){
var link = "";
var params = {
Destination: {
ToAddresses: [ "address#gmail.com" ]
},
Message: {
Body: {
Text: { Data: link
}
},
Subject: { Data: " text + " has arrived!" }
},
Source: "verifiedaddress#gmail.com"
};
return SES.sendEmail( params ).promise();
}

Related

SES detect new email coming and do something

I want to create a simple system like this
Using SES to receive email (no need sending email)
Once new email coming, get email body and POST to another Api.
Anyone know what is the technical to do something like that?
I found the answer now.
Config SES to accept receiving emails
Using S3 as email server to store emails.
On Email Receiving configuration (AWS console), Create rules set when new email coming and do 2 things
Store email to s3 bucket
Trigger Lambda function. Lambda function can read messageId but could not read email body, so need to use messageId as a Bucket Key and get body from s3 Bucket.
Do something you want with email body.
Code for lambda function to read email body. Other steps are no need any code, just setting on AWS console
import * as AWS from "#aws-sdk/client-s3";
const bucket = "email-storing-bucket-name";
async function getS3Object (bucketKey) {
return new Promise(async (resolve, reject) => {
const getObjectCommand = new AWS.GetObjectCommand({ Bucket: bucket, Key: bucketKey });
try {
const s3 = new AWS.S3({});
const response = await s3.send(getObjectCommand);
let responseDataChunks = [];
response.Body.once('error', err => reject(err));
response.Body.on('data', chunk => responseDataChunks.push(chunk));
response.Body.once('end', () => resolve(responseDataChunks.join('')));
} catch (err) {
return reject(err);
}
});
}
export const handler = async(event, context) => {
console.log(JSON.stringify(event));
const bucketKey = event.Records[0].ses.mail.messageId
console.log("email bucketKey == ", bucketKey);
await getS3Object(bucketKey).then(data => {
console.log("data ==", data);
// TODO: Call webhook or do anything with email body here
});
};
Hope this help someone else.

Client authentication failed in Postman request for Amazon Alexa Smart Home Skill LWA

I am referring to Amazon documentation for the purpose of Customer Authentication. Currently, I am using LWA.
Steps I followed:
I enabled the Send Alexa Events Permission from the Alexa developer Console in Build > Permission page.
I took the grant code from the request in the cloudwatch logs which was sent when I logged in using Alexa companion app.
Example:-
{
"directive": {
"header": {
"messageId": "Example",
"name": "AcceptGrant",
"namespace": "Alexa.Authorization",
"payloadVersion": "3"
},
"payload": {
"grant": {
"code": "Example2",
"type": "OAuth2.AuthorizationCode"
},
"grantee": {
"token": "Example3",
"type": "BearerToken"
}
}
}
}
Permission Page under build on Alexa Developer console gave me client-Id and client-secret Which I used for making the post request to https://api.amazon.com/auth/o2/token.
Example:-
POST /auth/o2/token HTTP/l.l
Host: api.amazon.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
grant_type=authorization_code&code=&client_id=&client_secret=
I passed the code,client_id, and client_secret in the above example and made the post request to this URL https://api.amazon.com/auth/o2/token
I tried using x-www-form-urlencoded;charset=UTF-8 and also JSON for the Content-Type.
I followed the step given in the above documentation and I am stuck on the error ( 401 Unauthorized ):
{
"error_description": "The request has an invalid grant parameter : code",
"error": "invalid_grant"
}
I tried implementing it using Python code and Postman both. Ending up with the Same above error scenario.
Here is a sample code to help you and others who are looking to send events to alexa gateway.
const AWS = require('aws-sdk');
AWS.config.update({region: 'eu-west-1'});
// Create the DynamoDB service object
const ddb = new AWS.DynamoDB({ apiVersion: 'latest' });
const doc = new AWS.DynamoDB.DocumentClient({
convertEmptyValues: true,
service: ddb
});
// Using 'request' for http POST and GET request.
// https://www.npmjs.com/package/requests
// npm install --save requests
const r = require('request');
//Handle Authorization. Call this method from your lambda handler whenever you get Alexa.Authorization message. You will get this message only when you select permission to
//send events in your Smart Home Skill.
//Access to Event gateway allows you to enable Proactive Device Discovery and
//Proactive State Reporting in your skill
//More information on Alexa.Authorization can be found on https://developer.amazon.com/docs/device-apis/alexa-authorization.html
function handleAuthorization(request, context, user) {
//Even when you are using your own authentication, the url below will still
//point to amazon OAuth token url. The token you obtain here has to be stored
//separately for this user. Whenever sending an event to alexa event gateway you will
//require this token.
//URL below is for EU server. Look at following documentation link to identify correct url
//for your system.
//https://developer.amazon.com/docs/smarthome/send-events-to-the-alexa-event-gateway.html
var url = "https://api.amazon.com/auth/o2/token";
var body = {
grant_type : 'authorization_code',
code : request.directive.payload.grant.code,
client_id : 'your client id from permissions page on developer portal where you enable alexa events. This is id different than one you specify in account linking settings',
client_secret : 'client secret from permissions page'
}
//https://developer.amazon.com/docs/smarthome/authenticate-a-customer-permissions.html
r.post({
url: url,
form : body
}, function(error, response, b){
if (error) { return console.log(error); }
var body = JSON.parse(b);
var params = {
TableName: 'Devices',
Item: {
'id' : user,
'auth_token' : body.access_token,
'refresh_token' : body.refresh_token
}
}
log("DEBUG:", "Authorization Body", JSON.stringify(body));
log("DEBUG:", "Authorization Response", JSON.stringify(response));
log("DEBUG:", "Database Params", JSON.stringify(params));
// Call DynamoDB to add the item to the table
var putObjectPromise = doc.put(params).promise();
//Store auth_token and refresh_token in database. We will need these
//while sending events to event gateway.
//Send a success response.
putObjectPromise.then(function(data) {
var response = {
event: {
header: {
messageId: request.directive.header.messageId,
namespace: "Alexa.Authorization",
name: "AcceptGrant.Response",
payloadVersion: "3"
},
"payload": {
}
}
};
context.succeed(response);
}).catch(function(err) {
//TODO - Add a Authorization error response JSON here.
console.log(err);
});
});
}

AWS Lambda: SES not working when Lex Chatbot is published to Slack

AWS SES works with Lex Test Chatbot but after the chatbot is published with Slack app it does not work( doesn't trigger email service). However there does not seem to be any problem with Lambda function as I am getting the response text back in slack. And i don't think there is a way to check the error why slack is making the problem.
Lambda Function:
var aws = require('aws-sdk');
var ses = new aws.SES({
region: 'us-east-1'
});
exports.handler = function(event, context, callback) {
var eParams = {
Destination: {
ToAddresses: [event.currentIntent.slots.Email]
},
Message: {
Body: {
Text: {
Data: "Hi, How are you?"
}
},
Subject: {
Data: "Title"
}
},
Source: "abc#gmail.com"
};
var email = ses.sendEmail(eParams, function(err, data) {
if (err)
else {
context.succeed(event);
}
});
callback(null, {
"dialogAction": {
"type": "ConfirmIntent",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "message to convey to the user, i.e. Are you sure you want a large pizza?"
}
}
});
};
Edit 1: I figured the issue is that i am not getting the values in [event.currentIntent.slots.Email] when i publish my Lex bot on Slack.
Try to below steps to identify the root cause:
Make sure you have configured your bot with Slack correctly with this step-by-step tutorial.
If your bot works fine in your test bot (inside LEX) but not on Slack make sure you have published the latest version of your bot.
Try this below code on your AWS Lambda and see what you get in return.
callback(null, {
"dialogAction": {
"type": "ConfirmIntent",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "Echo: " + JSON.stringify(event.currentIntent.slots) <-- This
}
}
});
Hope this helps.
I had a similar problem where the bot worked in the lex console but not in slack. While unrelated to email this is what I discovered.
For some reason an empty session attributes
is stored as NULL when passed to slack. Thus you can't add variables to it. You'd expect it to be {} so if it is NULL change it's value to {}
if(intentRequest.sessionAttributes == null){
intentRequest.sessionAttributes = {};
}
As per the above comment, this was what got my slack bot to line up with what my test lex chat was doing:
session_attributes = intent_request['sessionAttributes'] if intent_request['sessionAttributes'] is not None else {}

How can I publish to a MQTT topic in a Amazon AWS Lambda function?

I would like to have an easy command like I use in the bash to publish something to a topic on MQTT inside a AWS Lambda function. Along the lines of:
mosquitto_pub -h my.server.com -t "light/set" -m "on"
Background: I would like to turn a lamp on and off with Alexa. Alexa can start a Lambda function, and inside of this Lambda function I would like to start an MQTT publish, because the lamp can listen to a MQTT topic and react on the messages there.(Maybe there are easier solutions, but we are in a complicated (university) network which makes many other approaches more difficult)
If you are using Python, I was able to get an AWS Lambda function to publish a message to AWS IoT using the following inside my handler function:
import boto3
import json
client = boto3.client('iot-data', region_name='us-east-1')
# Change topic, qos and payload
response = client.publish(
topic='$aws/things/pi/shadow/update',
qos=1,
payload=json.dumps({"foo":"bar"})
)
You will also need to ensure that the Role (in your Lambda function configuration) has a policy attached to allow access to IoT publish function. Under IAM -> Roles you can add an inline policy to your Lambda function Role like:
{
"Version": "2016-6-25",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Publish"
],
"Resource": [
"*"
]
}
]
}
If you're using Node.js, this will work -
var AWS = require('aws-sdk');
var iotdata = new AWS.IotData({ endpoint: '*****************.iot.us-east-1.amazonaws.com' });
exports.handler = async(event) => {
console.log("Event => " + JSON.stringify(event));
var params = {
topic: "MyTopic",
payload: JSON.stringify(event),
qos: 0
};
return iotdata.publish(params, function(err, data) {
if (err) {
console.log("ERROR => " + JSON.stringify(err));
}
else {
console.log("Success");
}
}).promise();
};
Remember to add iot:publish permission to the role used by this lambda function.
The AWS SDK has two classes to work with IoT: Iot and IotData. IotData.publish is the method you are looking for. It looks like the Iot object is for working with things and IotData is for working with MQTT and shadows. This ought to be directly referenced in the documentation on MQTT and shadows, but it isn't.
This service (IotData) is also available in the CLI.
The previous post about nodeJS send the message 2 times for me.
Correction is here
var mqttParams = {
topic: topicName,
payload: JSON.stringify(event),
qos: 1
};
const request = iotdata.publish(mqttParams);
request
.on('success', () => console.log("Success"))
.on('error', () => console.log("Error"))
return new Promise(() => request.send());
This worked for me using Rust.
main.rs:
use lambda_http::aws_lambda_events::serde_json;
use lambda_runtime::{service_fn, Error, LambdaEvent};
use serde_json::Value;
#[tokio::main]
async fn main() -> Result<(), Error> {
let func = service_fn(func);
lambda_runtime::run(func).await?;
Ok(())
}
async fn func(_event: LambdaEvent<Value>) -> Result<(), Error> {
let config = aws_config::load_from_env().await;
let client = aws_sdk_iotdataplane::Client::new(&config);
let publish = client
.publish()
.topic("topic")
.qos(1)
.payload(aws_smithy_types::Blob::new("payload"));
publish.send().await?;
Ok(())
}
Cargo.toml:
[package]
name = "MyLambda"
version = "0.1.0"
edition = "2021"
[dependencies]
lambda_http = { version = "0.7", default-features = false, features = ["apigw_http"] }
lambda_runtime = "0.7"
tokio = { version = "1", features = ["macros"] }
aws-config = "0.51.0"
aws-sdk-iotdataplane = "0.21.0"
aws-smithy-types = "0.51.0"
If you use Node.js, you need to install the mqtt library. The following steps help you download and install mqtt library on AWS Lambda.
Download and install Node.js and npm on your PC.
Download MQTT library for node.js.
Unzip it at the nodejs directory that Node.js was installed. (In Windows 10 x64, nodejs directory is C:\Program Files\nodejs)
Create a folder to store the mqtt installed files. For example, D:\lambda_function.
Run Command Prompt as administrator, change directory to nodejs directory.
Install mqtt library to D:\lambda_function.
C:\Program Files\nodejs>npm install --prefix "D:\lambda_function” mqtt
Here's a similar project.
Here is a simple JavaScript code using async await:
const AWS = require("aws-sdk");
exports.handler = async event => {
try {
const iotData = new AWS.IotData({ endpoint: "IOT_SERVICE_ID-ats.iot.eu-central-1.amazonaws.com" });
const params = {
topic: 'some/topic',
payload: JSON.stringify({ var1: "val1" })
}
result = await iotData.publish(params).promise();
console.log(result);
return { statusCode: 200, body: `IoT message published.` };
} catch (e) {
console.error(e);
return { statusCode: 500, body: `IoT message could not be published.` };
}
};
Don't forget to give this lambda the required iot:publish permission to publish to this IoT topic.

How can i confirm the subscription request HTTP from amazon SNS

I have been searching all over the web and nothing gives a clear answer to confirm the subscription request from amazon SNS. I already send the subscription from the amazon console to my website, but what's next? I am using amazon EC2 as my server with PHP.
Before you even configure the HTTP/HTTPS endpoint subscription through AWS management console, you need to make sure that the HTTP or HTTPS endpoint of your PHP web site has the capability to handle the HTTP POST requests that Amazon SNS generates. There are several types of SNS messages: SubscriptionConfirmation, Notification and UnsubscribeConfirmation. Your PHP code needs to get the header x-amz-sns-message-type from request and process it based on the message type. For SubscriptionConfirmation message, your PHP application needs to process the POST message body, which is a JSON document. In order to subscribe the topic, your PHP code needs to visit the "SubscriberURL" specified in the JSON body. Optionally, you should verify the signature to make sure the authenticity of message before subscribing the topic.
You can find more details on AWS documentation: http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html
Here is an express application (Node.js) which confirms the SNS subscription:
const express = require('express')
const request = require('request')
// parse urlencoded request bodies into req.body
const bodyParser = require('body-parser')
const app = express()
const port = 8080
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.post('/', (req, res) => {
let body = ''
req.on('data', (chunk) => {
body += chunk.toString()
})
req.on('end', () => {
let payload = JSON.parse(body)
if (payload.Type === 'SubscriptionConfirmation') {
const promise = new Promise((resolve, reject) => {
const url = payload.SubscribeURL
request(url, (error, response) => {
if (!error && response.statusCode == 200) {
console.log('Yess! We have accepted the confirmation from AWS')
return resolve()
} else {
return reject()
}
})
})
promise.then(() => {
res.end("ok")
})
}
})
})
app.listen(port, () => console.log('Example app listening on port ' + port + '!'))
To use it one needs to install required packages:
yarn add express request body-parser
Once you confirm the subscription AWS will send a POST request to the server with the following content:
{
"Type": "SubscriptionConfirmation",
"MessageId": "XXXXXXXX-1ee3-4de3-9c69-XXXXXXXXXXXX",
"Token": "SECRET_TOKEN",
"TopicArn": "arn:aws:sns:us-west-2:XXXXXXXXXXXX:ses-test",
"Message": "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:XXXXXXXXXXXX:ses-test. To confirm the subscription, visit the SubscribeURL included in this message.",
"SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:XXXXXXXXXXXX:ses-test&Token=SECRET_TOKEN",
"Timestamp": "2018-11-21T19:48:08.170Z",
"SignatureVersion": "1",
"Signature": "SECRET",
"SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.pem"
}
The payload contains SubscribeURL which is requested by the server.
The end point you have specified will get data from AWS SNS endpoint verification service, The same end point will be used to verify the end point and to get notifications from aws,
Simply dump the input sent by AWS SNS into one text file like,
$json_write_to_text = json_decode(file_get_contents("php://input"));
You will find all data sent by AWS SNS, but just find SubscriptionUrl (which will be specific for endpoint having valid token), Open this in browser you will have SubscriptionConfirmation status. That's it
Enjoy.
Spring cloud SNS subscription with annotation
spring cloud AWS has support for auto confirmation of subscriber, you just need to put this annotation "#NotificationSubscriptionMapping"
#Controller
#RequestMapping("/topicName")
public class NotificationTestController {
#NotificationSubscriptionMapping
public void handleSubscriptionMessage(NotificationStatus status) throws IOException {
//We subscribe to start receive the message
status.confirmSubscription();
}
#NotificationMessageMapping
public void handleNotificationMessage(#NotificationSubject String subject, #NotificationMessage String message) {
// ...
}
#NotificationUnsubscribeConfirmationMapping
public void handleUnsubscribeMessage(NotificationStatus status) {
//e.g. the client has been unsubscribed and we want to "re-subscribe"
status.confirmSubscription();
}
}
http://cloud.spring.io/spring-cloud-aws/spring-cloud-aws.html#_sns_support
I solved this using NodeJS backend. Lets say you have an API like this in HapiJS (Well it doesnt matter you can have another tech)
{
method: 'POST',
path: '/hello',
handler: ( request, reply ) => {
reply( Hello.print(request.payload) );
},
config: {
tags: ['api']
}
}
Just pass the payload you receive, on to your business logic.
In the business logic process it like this
'use strict';
const request = require('request');
exports.print = (payload) => {
payload = JSON.parse(payload);
if(payload.Type === 'SubscriptionConfirmation'){
return new Promise((resolve, reject) => {
const url = payload.SubscribeURL;
request(url, (error, response) => {
if (!error && response.statusCode == 200) {
console.log('Yess! We have accepted the confirmation from AWS');
return resolve();
}
else
return reject();
});
});
}
I am using request module from NPM to automatically accept such requests.
Another way would be to print the contents of payload and then click on the URL given in payload.SubscribeURL.
Once AWS accepts it you check the confirmation on the Subscriptions page where Subscription ARN would be changed from Pending Confirmation to a complex name-cum-SHA having your Topic name.