[メモ] ほぼ無料?Alexaスキル内でメール送信を試してみた(検証目的)

2019.02.05

こんにちは、Mr.Moです。

Alexaスキル内でメール送信を試しにやってみましたので備忘録として残しておきます。 あくまでも検証用の目的で作ったものなので、公開目的のスキルの場合は使うサービスの入れ替えやケアがさらに必要になると思います。

使ったツール・サービス

freenomでドメイン取得

まずドメインを取得したいのですが、検証目的なのでお金をかけたくありません。 なので無料でドメインを取得できるサービスfreenomを利用します。

ドメイン取得手順

下記のURLから無料のドメインを取得していきます。 https://my.freenom.com/domains.php

検証用なので、無料期間3ヶ月もあれば充分です。

無事、Orderできましたね。取得したドメインを確認しましょう。

My Domainsを選択し、

先程、取得したドメインが表示されてますね!

Amazon SES の設定

次にAmazon SESの設定をしていきます。

設定手順

まずAmazon SESのコンソール画面を表示しましょう。あとは下記の手順で設定します。

ここで各種DNSレコード情報が表示されます。この情報をfreenomに設定します。

DNSレコード情報をfreenomに設定

freenomの画面に戻って、My Domainsの画面(先程取得したドメインを確認した画面)から作業を進めていきます。

freenomの設定はこれで終了です。

しばらくするとAmazon SES側のチェックが下記の画面のように通る(Statusがverifiedになる)かと思います。

メール送信してみる

メールが送信できる状態が整ったので早速、メール送信できるか試してみます。

まず送信先のメールアドレスを登録します。

下記のようにチェックが通れば(Statusがverifiedになれば)OKです。

それではおもむろにメール送信します。

メール送信できました!

なお、本格的にメール処理を使う際には下記から制限の解除を行う必要があります。 (この記事ではサンドボックスの環境で実施している状態です) SES Sending Limits Increase case

参考:Amazon SESに関する無料利用枠

Amazon SESは検証するだけなら充分な無料利用枠が設けられています。

1 か月あたり 62,000 件の送信メッセージ 1 か月あたり 1,000 件の受信メッセージ

Alexaスキルの用意

AWS CodeStarAWS Cloud9を使ってサクッとAlexaスキルを作成していきます。

AWS CodeStarを使ってAlexaスキルの雛形を作成

下記の記事を参考に作業を進めます。

AWS CodeStar 東京リージョンでAlexaスキルのテンプレートが選択できるようになったので試してみる

参考:AWS CodeStarに関係する無料利用枠

CodeStarを利用すると裏では複数のAWSのサービスが起動されています。その中でも下記サービスは利用料金が発生する可能性があるので、検証目的の今回は無料利用枠の範囲で使用したいと思います。

1 か月あたり 100 ビルド分のビルド

1 か月あたり 1 つのアクティブなパイプライン

Cloud9でコードの修正など

Cloud9はコードを修正できるエディターとコマンド(CLI)が打てるターミナルが付いているので、パッと作業したい時に非常に便利です。gitもデフォルトで入ってます。

CodeStarで作成されたAlexaスキルの雛形をgit cloneで作業用のCloud9環境に落として、必要なコード修正の作業を実施します。

  • root/interactionModels/custom/ja-JP.json
{
"interactionModel": {
"languageModel": {
"invocationName": "メール送信テスト",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "HelloWorldIntent",
"slots": [],
"samples": [
"メール送信して"
]
}
],
"types": []
}
}
}
  • root/skill.json
{
"manifest": {
"publishingInformation": {
"locales": {
"en-US": {
"summary": "A basic skill made from Alexa Skills Kit template",
"examplePhrases": [
"Alexa open hello node",
"Alexa tell hello node hello",
"Alexa ask hello node say hello"
],
"name": "hello node",
"description": "A basic skill made from Alexa Skills Kit template"
},
"ja-JP": {
"summary": "メール送信テスト",
"examplePhrases": [
"アレクサ メール送信テストを開いて"
],
"name": "メール送信テスト",
"description": "メール送信テストの説明です"
}
},
"isAvailableWorldwide": true,
"testingInstructions": "Sample Testing Instructions.",
"category": "EDUCATION_AND_REFERENCE",
"distributionCountries": []
},
"permissions" : [
{
"name": "alexa::profile:email:read"
}
],
"apis": {
"custom": {
}
},
"manifestVersion": "1.0"
}
}
  • root/lambda/custom/index.js
// This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
// Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
// session persistence, api calls, and more.
const Alexa = require('ask-sdk-core');

const mailFrom = '<Fromのメールアドレス>';
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-1'});

const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = 'こんにちは、メール送信テストです。';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'HelloWorldIntent';
},
async handle(handlerInput) {
try {
// メールアドレスの取得
const upsServiceClient = handlerInput.serviceClientFactory.getUpsServiceClient();
const email = await upsServiceClient.getProfileEmail();
const speechText = 'メールアドレス: ' + email + 'にメールを送信しました。';

// メール送信処理
const ses = new AWS.SES();
ses.sendEmail({
Source: mailFrom,
Destination: {
ToAddresses: [email]
},
Message: {
Subject: {
Data: 'このメールはAlexaスキルから送信されました。'
},
Body: {
Text: {
Data: 'Alexaスキルでメール送信テスト',
}
}
}
}, function(err, data) {
if (err) {
console.log(err, err.stack);
} else {
console.log("Success! Sent e-mail.");
}
});

return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
} catch (error) {
// メール情報へのアクセス権限の要求
return handlerInput.responseBuilder
.speak('メールアドレスの利用が許可されていません。アレクサアプリの設定を変更して下さい。')
.withAskForPermissionsConsentCard(['alexa::profile:email:read'])
.getResponse();
}
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speechText = 'You can say hello to me! How can I help?';

return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
|| handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speechText = 'Goodbye!';
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse();
}
};

// The intent reflector is used for interaction model testing and debugging.
// It will simply repeat the intent the user said. You can create custom handlers
// for your intents by defining them above, then also adding them to the request
// handler chain below.
const IntentReflectorHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
const intentName = handlerInput.requestEnvelope.request.intent.name;
const speechText = `You just triggered ${intentName}`;

return handlerInput.responseBuilder
.speak(speechText)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};

// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`~~~~ Error handled: ${error.message}`);
const speechText = `Sorry, I couldn't understand what you said. Please try again.`;

return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};

// This handler acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
HelloWorldIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler) // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
.addErrorHandlers(
ErrorHandler)
.withApiClient(new Alexa.DefaultApiClient())
.lambda();

上記のようにファイルの修正が終わったらgit pushします。あとは自動的にCI/CDが実行されるはずです。

参考:AWS Cloud9に関係する無料利用枠

Cloud9そのものには利用料金は発生しませんが、裏側で動くコンピュータリソースをAmazon EC2にした場合はEC2の利用料金がかかります。 Amazon EC2はAWS アカウント作成から 12 か月間の無料利用枠があります。 (12 か月間後もCloud9の料金は安価なので、自分はCloud9をよく使っています。)

750 時間/月の Linux、RHEL、SLES の t2.micro インスタンスの使用 750 時間の Windows t2.micro インスタンスの使用

Alexa Developer Consoleでの作業

アクセス権限の設定画面で「ユーザーのEメールアドレス」を有効にしておきましょう。

ちなみに、Alexaアプリの方でもスキルに権限許可をする必要があります。

Alexaスキルからメール送信実行

いよいよ、Alexaスキルからメール送信の実行をしていきます。

Alexaスキルからメール送信できました!

まとめ

Alexaスキルでメール送信する必要最低限の状態が整いました。おそらくココまではほぼ無料でいけた気がします。 メール処理に関してこれから色々検証をしていく予定です!