This guide assists in the development of an Alexa skill, focusing on the given example of this skill.
All infrastructure is deployed using Terraform (except the IAM Users and policies required to deploy via Terraform). All the terraform files live on the terraform
directory. Necessary resources are:
archive_file
--> Allows zipping the contents of the Lambda Function.aws_lambda_function
--> Lambda Function that handles the user inquiry via Alexa.aws_lambda_permission
--> Permissions to invoke the Lambda Function. This includes verification via the skill ID.aws_cloudwatch_log_group
--> Log Group in CloudWatch to store logs for the Lambda Function.aws_s3_bucket
--> Storage for the Terraform state. Note this is not necessary if you intend on working exclusively with local state, but this will not work correctly with a CI/CD pipeline.aws_iam_role
--> IAM Role to allow invoking the necessary resources for the Lambda Function.aws_iam_policy
--> Two IAM Policies for the necessary permissions.aws_iam_policy_document
--> IAM Policy Document that allows Lambda to assume the given role.
These are the necessary permissions to correctly deploy the stack in a CI/CD pipeline after deploying most of the resources with an account with enough permissions. Permissions are broken down on several IAM policies for simplicity:
policy-alexa-my-plants-iam-deployer
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EditPermissions",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:GetPolicyVersion",
"iam:ListRoleTags",
"iam:UntagRole",
"iam:GetPolicy",
"iam:TagRole",
"iam:UpdateRoleDescription",
"iam:CreateRole",
"iam:PutRolePolicy",
"iam:TagPolicy",
"iam:CreatePolicy",
"iam:PassRole",
"iam:ListPolicyVersions",
"iam:ListAttachedRolePolicies",
"iam:UntagPolicy",
"iam:UpdateRole",
"iam:ListRolePolicies",
"iam:GetRolePolicy"
],
"Resource": [
"arn:aws:iam::REDACTED_ACCOUNT_ID:policy/policy-alexa-my-plants-lambda-dynamod",
"arn:aws:iam::REDACTED_ACCOUNT_ID:policy/policy-alexa-my-plants-lambda-execution",
"arn:aws:iam::REDACTED_ACCOUNT_ID:role/alexa-skill-my-plants-lambda-role"
]
},
{
"Sid": "ListPermissions",
"Effect": "Allow",
"Action": [
"iam:ListPolicies",
"iam:ListRoles"
],
"Resource": "*"
}
]
}
policy-alexa-my-plants-lambda-deployer
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EditPermissions",
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:TagResource",
"lambda:ListVersionsByFunction",
"lambda:GetFunction",
"lambda:UpdateFunctionConfiguration",
"lambda:UntagResource",
"lambda:GetFunctionCodeSigningConfig",
"lambda:UpdateFunctionCode",
"iam:PassRole",
"lambda:AddPermission",
"lambda:ListTags",
"lambda:RemovePermission",
"lambda:GetPolicy"
],
"Resource": [
"arn:aws:lambda:REDACTED_REGION:REDACTED_ACCOUNT_ID:function:alexa-skill-my-plants",
"arn:aws:iam::REDACTED_ACCOUNT_ID:role/alexa-skill-my-plants-lambda-role"
]
},
{
"Sid": "ListPermissions",
"Effect": "Allow",
"Action": "lambda:ListFunctions",
"Resource": "*"
}
]
}
policy-alexa-my-plants-logs-deployer
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EditPermissions",
"Effect": "Allow",
"Action": [
"logs:ListTagsLogGroup",
"logs:TagLogGroup",
"logs:UntagLogGroup",
"logs:CreateLogGroup"
],
"Resource": "arn:aws:logs:REDACTED_REGION:REDACTED_ACCOUNT_ID:log-group:/aws/lambda/alexa-skill-my-plants:*"
},
{
"Sid": "ListPermissions",
"Effect": "Allow",
"Action": "logs:DescribeLogGroups",
"Resource": "*"
}
]
}
policy-alexa-my-plants-storage-deployer
Note in this case that several permissions are necessary for Terraform to interact with the backend where the state lives.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DynamodGetPermissions",
"Effect": "Allow",
"Action": [
"dynamodb:Describe*",
"dynamodb:List*",
"dynamodb:PartiQLSelect",
"dynamodb:Get*",
"dynamodb:BatchGetItem",
"dynamodb:ConditionCheckItem",
"dynamodb:Scan",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:REDACTED_REGION:REDACTED_ACCOUNT_ID:table/alexa-skill-my-plants-table"
]
},
{
"Sid": "DynamodPutPermissions",
"Effect": "Allow",
"Action": [
"dynamodb:UpdateTimeToLive",
"dynamodb:UntagResource",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteTable",
"dynamodb:UpdateTableReplicaAutoScaling",
"dynamodb:CreateTable",
"dynamodb:UpdateGlobalTableSettings",
"dynamodb:TagResource",
"dynamodb:PartiQLSelect",
"dynamodb:UpdateGlobalTableVersion",
"dynamodb:UpdateTable"
],
"Resource": [
"arn:aws:dynamodb:REDACTED_REGION:REDACTED_ACCOUNT_ID:table/alexa-skill-my-plants-table"
]
},
{
"Sid": "PutPermissions",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObjectAcl",
"s3:GetObject",
"s3:PutBucketTagging",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::alexa-skill-my-plants-terraform-backend/*",
"arn:aws:s3:::alexa-skill-my-plants-terraform-backend"
]
},
{
"Sid": "GetPermissions",
"Effect": "Allow",
"Action": [
"s3:GetEncryptionConfiguration",
"s3:GetLifecycleConfiguration",
"s3:Get*Configuration",
"s3:GetAccelerateConfiguration",
"s3:GetBucket*"
],
"Resource": [
"arn:aws:s3:::alexa-skill-my-plants-terraform-backend",
"arn:aws:s3:REDACTED_REGION:REDACTED_ACCOUNT_ID:storage-lens/*"
]
}
]
}
A terraform.tfvars
file is heavily encouraged as to simplify the development process and secret management. The contents should be the following:
account_id = "XXXX"
region = "XXXX"
backend_url = "XXXX"
backend_key = "XXXX"
skill_id = "ALEXA_SKILL_ID"
If you intend to run Terraform for the first time before migrating the state to a backend. Follow these steps:
- Initialize the stack (
terraform init
). - Temporarily remove the
backend.tf
file from your configuration. - Run the stack (
terraform apply
). - Add back the
backend.tf
file. - Initialize the stack again (
terraform init
) and opt to migrate the state to the backend.
To configure a specific profile, use the following command and following the instructions:
aws configure --profile alexa-deployer
Once the profile is set, create an environment variable for the AWS profile:
export AWS_PROFILE="alexa-deployer"
Then you can reconfigure Terraform with terraform init -reconfigure
and run the init or plan commands to see if the account has enough permissions.
Start by installing all dependencies:
npm i
It is possible to debug the Lambda function locally, but is simpler to do it using the Alexa inteface. In case you want to debug locally, follow this guide.
The skill handler (src/index.js
) is very simple to configure, as it mostly involves importing the intent handlers and adding them as possible request handlers.
const Alexa = require('ask-sdk-core');
const {
// ... handlers
} = require('./handlers');
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers
// ... handlers
()
.addErrorHandlers(ErrorHandler)
// API Client to use other features from Alexa
.withApiClient(new Alexa.DefaultApiClient())
.lambda();
Intent handlers are the handlers that take care of each of the requests that a user might generate.
All intent handlers live on src/handlers
as something.handler.js
for organization. All handlers have the same structure:
const CustomHandler = {
// Determines whether this handler can actually handle the intent or not
canHandle(handlerInput) {
return (
Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
Alexa.getIntentName(handlerInput.requestEnvelope) === 'MyCustomIntent'
);
},
// Actually handles the intent and does something
handle(handlerInput) {
const speechText = `My custom speech text`;
const cardText = `My custom card text`;
// Returns a special Alexa response
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard(cardText, speechText)
.getResponse();
}
};
module.exports = CustomHandler;
Files within src/utils
and src/locales
are mostly utilities to help with the development.
By using Terraform, the skill configuration process is simplified significantly, as much of the configuration comes from the infrastructure. These are the steps to configure and release the Alexa skill:
- Create an Amazon Developer Account.
- Sign in to the Alexa developer console.
- Within the Skills tab, click on Create Skill.
- On the Create a new skill page, provide the following information:
- For Skill name, enter
My Custom Skill
in the box. - For Primary locale, choose English (US).
- For Choose a model to add to your skill, choose Custom.
- For Choose a method to host your skill's backend resources, choose Alexa-hosted (Node.js).
- In the upper-right corner, click Create skill.
- For Skill name, enter
- On the Choose a template to add to your skill page, leave the Start from Scratch template selected.
- In the upper-right corner, click Continue with template.
- Assign a skill invocation name.
- On the Build tab, in the left pane, click CUSTOM > Invocations > Skill Invocation Name.
- For Skill Invocation Name, enter
my custom skill
. - To save and build your model, click Save Model, and then click Build Model.
- To deploy your skill code, open the Code tab, and then, in the upper-right corner, click Deploy.
- Test your skill in the Alexa simulator.
- Open the Test tab.
- To enable your skill for testing, locate Test is disabled for this skill, and then, from the drop-down list, select Development.
- To test with voice, in the Will you allow developer.amazon.com to use your microphone? pop-up, select Allow.
- In the Alexa Simulator pane, enter
open my custom skill
, but omitAlexa
from the string Or, to use voice, click and hold the microphone, and then speak an utterance. - If the test is successful, Alexa says,
"OK."
- Repeat steps 4 and 5 for the utterances
hello
,help
, andcancel
. - If the skill doesn't perform as expected, see Troubleshooting.
- Ensure that the model has no utterance conflicts (you might not see this option if there are no conflics).
- Open the Build tab.
- In the left pane, click CUSTOM > Interaction Model > Utterance Conflicts (0).
- If any number other than (0) appears next to the term Utterance Conflicts, click the error message for details.
- Resolve the conflicts, and then press Ctrl+R to refresh your browser window.
- Repeat until you see Utterance Conflicts (0).
- Supply metadata to your skill.
- Open the Distribution tab.
- On the English (US) Store Preview page, in the Public Name box, enter
My Custom Skill
. - In the One Sentence Description box, enter the appropriate text.
- In the Detailed description box, enter the appropriate text.
- Leave the What's New box blank.
- In the Example Phrase 101 pane, click More.
- One by one, enter the example launch phrases. After each entry, to add a new phrase, click the plus sign (+). Some examples could be
Alexa, open my custom skill
,Alexa, ask my custom skill to start
, orAlexa, launch my custom skill
. - In the Category box, choose Social > Communication.
- In the Keywords box, enter some appropriate keywords.
- Add a custom icon. (optional)
- If you don't have logo art of your own, use the free Alexa Icon Builder to create it.
- In a new browser tab, open the Alexa Icon Builder.
- In the keyword search box, search for an icon and select it.
- In the Icon menu at right, to open the color window, click the box next to Icon.
- To set the icon color, click Solid color, and then click to choose a color.
- Click anywhere to close the color selector.
- To choose a background color, click the box next to Icon background, click Solid color, and then click to choose a color.
- To choose a border color, click the box next to Icon border, click Solid color, and then click to choose a color. Or, to remove the border, move the slider to the left.
- To add an icon shadow, click the box next to Icon shadow, and then click to choose a color. Or, to remove the shadow, move the slider to the left.
- To download the icons in two sizes, click Download. Alexa creates logo files for you in the correct sizes, and then stores them in a .zip file.
- On your computer, unzip the images file.
- In the developer console, open Distribution > Skill Preview.
- Drag each icon file into its designated space.
- Click Save and continue, and then view your finished icons.
- Supply privacy, terms of use, compliance, and availability data for your skill.
- Open the Distribution tab.
- Click Skill Preview > English (US).
- In the Privacy Policy URL box, if you have a privacy policy, enter a URL, or if you don't, leave the box blank. A privacy policy is optional unless your skill links to users' accounts or collects user information. This skill does not.
- In the Terms of Use URL box, if you have a terms of use agreement, enter a URL, or if you don't, leave the box blank. A terms of use agreement is optional. If you do require consent to such an agreement, however, you must supply it separately for each locale.
- On the Distribution tab, click Privacy & Compliance.
- Answer each question appropriately.
- Select the Export Compliance check box.
- In the Testing Instructions box, enter the following lines.
System requirements: None To begin, say "Alexa, open Hello World."
- Click Save and continue.
- Enable permissions to ask for the device location.
- Within the Build tab, go to TOOLS > Permissions.
- Find the option that reads
Country/Region & Postal Code Only
and check it.
- Copy the Skill ID for the Lambda Function. Your Lambda trigger will use skill verification, you need the Skill ID for this.
- Within the Build tab, go to CUSTOM > Endpoint.
- Find the text that reads
Your Skill ID
and copy the value. It should look something like thisamzn1.ask.skill.ID_HERE
. - Keep this value handy for Terraform, but don't share it.
You can test your skill using the Alexa Simulator. You can find it within the Test tab in the Alexa Developer Console. If it seems disabled, use the dropdown to set testing for development.
This repository contains 3 main workflows:
automatic-release
--> Runs the Terraform stack to deploy changes and generates a new release with release notes.linting
--> Lints the JavaScript code to make sure it follows a consistent format.testing
--> Validates the Terraform stack to make sure format and contents are valid.
There is an extra workflow, but this is automatically added by GitHub Pages to build the page.