Alexa-HostedスキルからEメールを送信する

Alexa-Skill

はじめに

スキルからユーザーに何らかの情報を返すとき、音声で読み上げるには長すぎたり、記録として手元に残しておきたい場合があります。このような場合に、スキルの返したい情報をEメールで送る方法について紹介します。

Eメールの宛先は、デバイスを所有するアカウント(オーナー)のEメールアドレスになります。またEメールの送信には、Amazon Simple Email Service (SES) を使用します。

Alexaは音声プロフィールを登録していると、個々の話者を特定できます。ユースケース的には、個々の話者にEメールを送れると面白いのですが、話者ごとのEメールアドレスを取得する機能はAlexaにはありません。全てデバイスオーナー宛にEメールされるため、複数ユーザーで利用している場合は、利用上の注意が必要です。

なお本記事ではAlexa-Hostedスキルを前提にしていますが、自前のLambda環境で稼働するスキルの場合も、同様の手法を適用可能です。

事前準備

読者はすでにカスタムスキルの開発経験があるという想定です。次のアカウント及び環境について、準備と理解がされているものとします。

構築手順

下記の手順に従って構築を進めます。

  • Eメールアドレスへのアクセス権の取得
  • SESへのアクセス権の付与
  • SESのセットアップ
  • スキルの実装
  • テスト

Eメールアドレスへのアクセス権の取得

デバイスオーナーのEメールアドレスにアクセスするには、デバイスオーナーによる許可を得る必要があります。下記のステップにより許可を得ることができます。

  • 開発者コンソールでEメールアドレスへのアクセス権をリクエストする
  • ユーザーがアクセス権を付与する
    • AlexaアプリでEメールアドレスへのアクセス権をユーザーが付与する、または
    • スキル内でユーザーに対して、Eメールアドレスへのアクセス権をリクエストする

以下、それぞれのステップを見ていきます。

開発者コンソールでアクセス権をリクエスト

開発者コンソールで、対象のスキルのビルドタブを選び、左側のメニューからモデルをクリックします。

メニューが切り替わるので、更にアクセス権限をクリックします。アクセス権をユーザーにリクエストする項目がリストされていますから、その中からユーザーのEメールアドレスを選んでオンにします。

ここでの設定は、あくまでスキルがEメールアドレスへのアクセス権を必要としていることを、宣言するだけです。実際にEメールアドレスにアクセスするには、この後に述べるように、ユーザーにアクセス権を付与してもらう必要があります。

Alexaアプリでアクセス権を付与

Alexaアプリからスキルを有効にすると、アクセス権の付与画面が表示され、Eメールアドレスへのアクセス権を付与できます。Alexaアプリからのアクセス権の付与は何時でもできます。

スキルの中からアクセス権をリクエスト

ユーザーがスキルの有効化時に、AlexaアプリからEメールアドレスへのアクセス権を付与しなかった場合、スキルの実行時にユーザーからのアクセス権の付与を得る必要があります。このために、スキルからAlexaアプリにアクセス権をリクエストするカードを表示します。

スキルからのレスポンスを下記に示します。.withAskForPermissionsConsentCard()を含めることで、AlexaアプリにEメールアドレスへのアクセス権を求めるカードを表示します。

handlerInput.responseBuilder
    .speak('Eメールへのアクセス権が必要です。アレクサアプリに、アクセス権を求めるカードを送ったので、許可をお願いします。')
    .withAskForPermissionsConsentCard(['alexa::profile:email:read'])
    .withShouldEndSession(true)
    .getResponse();

Alexaアプリには下記のようなカードが表示されます。ユーザーは許可をアップデートをクリックします。

アクセス権を付与できます。

SESへのアクセス権の付与

スキルからSES経由でEメールを送信するには、スキルにSESへのアクセス権を付与する必要があります。これはSecurity Token Service (STS) を使って実現します。

  • SESからメールを送信するポリシー (AlexaEmailWriteAccess) を定義する
  • ロール (AlexaEmailSender) を定義して、上記ポリシー (AlexaEmailWriteAccess) をアタッチする
  • 上記ロールの信頼関係にスキルの実行ロールを指定し、スキルが上記ロールを獲得できるようにする

STSを使ったAWSリソースへのアクセスについては、下記についても参照してください。

もし自前のLambda環境でスキルを実行する場合は、スキルの実行ロールにSESへのアクセス権を直接含めることで、STSによるアクセス権の付与は不要となります。

(本記事の以前のバージョンでは、SESにアクセスできるIAMユーザーを作成し、その認証情報をスキルコードで使っていました。しかしこの方式はAWSでは非推奨とされているため、STSを使う方式に変更しました)

STS版の記事をアップして直ぐにAmazonからメールが飛んできました。

Subject: ACTION REQUIRED: Your AWS Access Key is Exposed for AWS Account 123456789012

GitHubにサンプルコードを公開する際に、旧版のIAMユーザーの認証情報の入ったファイルを間違ってアップしていました。アップした直後に気がついて、レポジトリごと作り直したのですが、その前に検出されてしまったようです。すぐさまIAMコンソールで認証情報自体を無効化しました。

GitHubやNPMのレポジトリは、間違って公開された認証情報が無いか、スキャンされていると何かで読んだことを思い出しました。不正利用を企む組織の場合もあるそうです。

STSならばスキル側に認証情報を持つことは無いので、このようなミスはそもそも発生しません。図らずもSTSによるセキュリティ担保の必要性を認識することになりました。

ポリシーの作成

AWSコンソールからIAM (Identify and Access Management) サービスのコンソールを表示します。

  • 左メニューから①ポリシーを選択します
  • ポリシーを作成をクリックします
  • ビジュアルエディタタブを選択します
  • サービスを展開しSES V2を選択します
  • アクションを展開し、SendEmailを選択します
  • リソースを展開し、指定このアカウント内のいずれかを選択します
  • 次のステップ:タグをクリックします

タグはレポートなどで関連するリソースをまとめる際に使います。ここでは指定してもしなくても良いです。

次のステップ: 確認をクリックします。

  • 名前に①AlexaEmailWriteAccessを入力します
  • ポリシーの作成をクリックします

ポリシーAlexaEmailWriteAccessが作成され一覧に追加されます。

ロールの作成

次にAlexaEmailSenderロールを作成し、AlexaEmailWriteAccesポリシーをアタッチします。

AWSコンソールからIAMサービスのコンソールを表示します。

  • 左メニューから①ロールを選択します
  • ロールを作成をクリックします
  • 信頼されたエンティティの種類を選択から①AWSサービスを選択します。
  • 一般的なユースケースから②Lambdaを選択します
  • 次のステップ:アクセス権限をクリックします
  • ポリシーの一覧から、先に作成した①AlexaEmailAcceessを選択します
  • 次のステップ:タグをクリックします

タグはレポートなどで関連するリソースをまとめる際に使います。ここでは指定してもしなくても良いです。

次のステップ:確認をクリックします。

  • ロール名に①AlexaEmailSenderを入力します
  • ロールの作成をクリックします
  • ロールが作成され一覧にAlexaEmailSenderが表示されます
  • AlexaEmailSenderをクリックし概要を開きます
  • 信頼関係タブを選択します
  • 信頼関係の編集をクリックします
  • 信頼関係の編集画面で、①スキルの実行ロールARNを入力します (実行ロールARNの取得方法はこの後に説明します)
  • 信頼ポリシーの更新をクリックします
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<Skillの実行ロールARN>"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Alexa-Hostedスキルの実行ロールARNは、Alexaの開発者コンソールから取得できます。

  • 開発者コンソールで①コードエディタタブを選択します
  • aws integrateをクリックします
  • ポップアップから③コピーをクリックします

概要画面に戻ると信頼されたエンティティに、スキルの実行ロールARNが表示されていることが確認できます。

SESのセットアップ

下記のAWSのチュートリアルに従ってSESをセットアップします。

Eメールを送信する

ネット上には他にも詳しい解説記事が多数あるので参考になると思います。

チュートリアルの中でも触れられていますが、チュートリアルでセットアップされるのは、サンドボックスという検証用の環境です。このサンドボックスには幾つか利用上の制限があります。そのひとつが、SESで事前に登録・検証したEメールアドレスにしかメールを送れないというものです。テスト目的でしたら問題ありませんが、実際の運用では任意のデバイスオーナーのEメールアドレスにメールを送る必要があります。認定申請の前には、プロダクションに移行して、この制限を解除しておく必要があります。

プロダクションへの移行

サンドボックスからプロダクションへの移行申請のステップを説明します。下記の資料も参考にしてください。

SESダッシュボードを開き、左側のメニューからSending Statisticsをクリックします。

Edit your account detailsをクリックします。

申請フォームが開くので記入して送信します。

筆者が申請したのは随分前なので、どういった内容を入力したかは忘れてしまいました。もし今から申請するならこんなところかな、と思われるところを書いてみます。

Enable Production AccessYes
Mail TypeTransactional
Website URL公開済みのスキルの場合は、そのスキルのホームページ
未公開の場合は、ブランク
Use Case DescriptionUsecase
Amazon Alexaのカスタムスキルから、Alexaデバイスのオーナー宛にメールを送信します。
How do you plan to build or acquire your mailing list?
宛先のメールアドレスは、オーナーからの許可を得て、Alexaサービスから取得します。
How do you plan to handle bounces and complaints?
メールアドレスは毎回Alexaサービスから最新のものを取得します。またスキルの利用規約に問い合わせ先のメールアドレスを記載します。
How can recipients opt out of receiving email from you?
オーナーは、何時でもAlexaアプリからメールアドレスへのアクセス許可を取り消せます。
How did you choose the sending rate or sending quota that you specified in this request?
送信レートは、想定されるスキルの利用ユーザー数から、デフォルトのクオータで開始可能と見積もっています。
Additional Contact Addressブランク
Preferred contact languageJapanese

Use Case Descriptionの質問は、参照先の記事にあったものです。最後の質問、”How did you choose the sending rate or sending quota that you specified in this request?”は、ちょっと違和感があります。申請フォームにはクオータを指定するところはありませんから。

スキルの実装

スキルの定義をGitHubのレポジトリに登録しています。

GitHubのレポジトリ

対話モデルの定義

対話モデルにSendEmailIntentを定義しています。発話サンプルは下記の3つです。

  • メール
  • メールを送って
  • メールして

環境変数の定義

下記の項目を.envの中に定義して、環境変数として取り込んでいます。

  • メールの送信元のEメールアドレス
  • AlexaEmailSenderRoleのARN
  • SESをプロビジョニングしたリージョン
EmailSender='<送信元のEメールアドレス>'
RoleArn='<AlexaEmailSenderのARN>'
SESRegion='<SESをプロビジョニングしたリージョン>'

これらの環境変数は、スキルのコードの中に読み込まれます。

require('dotenv').config();
const EmailSender = process.env.EmailSender;
const RoleArn = process.env.RoleArn;
const SESRegion = process.env.SESRegion;

SendEmailIntentのハンドラ

SendEmailIntentに対するハンドラの中でEメールアドレスを取得し、Eメールを送信しています。Eメールアドレスが取得できなかった場合は、Eメールアドレへのアクセス権が無いとみなし、アクセス権を求めるカードをレスポンスに含めています。

const SendEmailItentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'SendEmailIntent';
    },
    async handle(handlerInput) {
        const emailAddress = await getEmailAddress(handlerInput);

        if (emailAddress) {
            await sendEmail(emailAddress);

            return handlerInput.responseBuilder
                .speak('Eメールを送信しました。')
                .withShouldEndSession(true)
                .getResponse();
        }
        else {
            return handlerInput.responseBuilder
                .speak('Eメールへのアクセス権が必要です。アレクサ・アプリに、アクセス権を求めるカードを送ったので、許可をお願いします。')
                .withAskForPermissionsConsentCard(['alexa::profile:email:read'])
                .withShouldEndSession(true)
                .getResponse();
        }
    }
};

Eメールアドレスへのアクセス権の確認

Eメールアドレスへのアクセス権があるかどうかは、Eメールアドレスにアクセスしてみないと判りません。getEmailAddress()関数の中で、サービスクライアント経由でアクセスし、403の例外が発生した場合は、アクセス権が付与されていないと判断しています。

const getEmailAddress = async (handlerInput) => {
    try {
        const upsServiceClient = handlerInput.serviceClientFactory.getUpsServiceClient();
        const emailAddress = await upsServiceClient.getProfileEmail();

        return emailAddress;
    }
    catch (error) {
        if (error.name === 'ServiceError') {
            if (error.statusCode === 403) {
                return null;
            }
        }

        throw error;
    }
}

Eメールの送信

Eメールを送信するsendEmail()関数を下記に示します。

const sendEmail = async (emailAddress) => {
    const params = {
        Source: EmailSender,
        Destination: {
            ToAddresses: [emailAddress],
        },
        Message: {
            Subject: {
                Data: 'テストメール',
                Charset: 'UTF-8'
            },
            Body: {
                Text: {
                    Data: 'こんにちは\nAlexaからのテストメールです。',
                    Charset: 'UTF-8'
                }
            }
        }
    };

    const STS = new AWS.STS({ apiVersion: '2011-06-15' });
    const assumeResp = await STS.assumeRole({
        RoleArn: RoleArn,
        RoleSessionName: 'SendEmailRoleSession'
    }, (err, res) => {
        if (err) {
            console.log('AssumeRole FAILED: ', err);
            throw new Error('Error while assuming role');
        }

        return res;
    }).promise();

    const credentials = {
        secretAccessKey: assumeResp.Credentials.SecretAccessKey,
        accessKeyId: assumeResp.Credentials.AccessKeyId,
        sessionToken: assumeResp.Credentials.SessionToken
    };

    const ses = new AWS.SES({
        apiVersion: '2010-12-01',
        credentials: credentials,
        region: SESRegion
    });

    const response = await ses.sendEmail(params).promise();

    console.log(JSON.stringify(response));
}

テスト

スキルをデプロイしたら、Alexaの開発者コンソールからテストします。

U: メール試験を開いて
A: こんにちは、メールして、と言ってみてください。
U: メールして
A: Eメールを送信しました

U: ユーザー、A: アレクサ

メールボックスにメールが配信されていることを確認してください。

おわりに

Alexaスキルからデバイスのオーナーに、Eメールを送る方法を紹介しました。

ユーザーにとっては、VUI (Voice User Interface)の中に閉じている(Alexaとの対話で完結する)ほうが、より良いインタフェースだと考えられます。ただやり取りされる情報の性質や量によっては、音声以外の手段で情報のやりとりをしたほうが良いこともあります。その場合は、外部のサービスやアプリケーションと連携したマルチモーダルなインタフェースを追求する必要があるでしょう。Eメールもその選択肢の1つとして考えています。

参考情報

付録. ソースコード

GitHubのレポジトリ

更新履歴

日付内容
2021/09/16STSを使用するように変更
2021/04/13SESユーザー作成の参考資料へのリンクを追加
2021/04/02初版公開                    

コメント

タイトルとURLをコピーしました