AWSのSESでサンクスメール配信のひな形作ってみた

諸般の事情でAWSのSESを使わないといけなくなっちゃって、検証がてら
いじっていて、結構ややこしかったので備忘録的に手順をまとめておきます。

全体の手順としては以下の様な感じ。SDK組み込んだプログラムからしか
SES送信用のAPIを叩けない様に設定しています。

1.SESの設定
2.SNSから呼び出すLambda作成
3.SNSの設定(BOUNCE対応)
4.DynamoDB作成(BOUNCE対応)
5.SESを使うLambdaの作成
6.Cognitoの設定
7.APIGateway設定
8.フロントプログラムの準備
9.SESのBOUNCE設定

◆作業手順(やや省略)

1.SES設定

先人が色々とまとめてくださっているので割愛、東京で使えるようになったので
東京リージョンで設定したほうが色々と楽です。
注意点としてはsandboxの解除申請を行うこと。これを忘れていると外部にメール
送信できません。

2.SNSから呼び出すLambda作成

BOUNCE時にBOUNCEになったメールアドレスをDynamoDBに登録する
Lambdaを準備します。

import datetime
import json
import os
import boto3

BOUNCE_TABLE = os.getenv('TABLE_NAME')
dynamodb = boto3.resource('dynamodb')

def lambda_handler(event, context):
  try:
    table    = dynamodb.Table(BOUNCE_TABLE)
    msg = event['Records'][0]['Sns']['Message']
    b = json.loads(msg)
    bounce_type = b['bounce']['bounceType']
    bounce_mail = b['bounce']['bouncedRecipients'][0]['emailAddress']

    if bounce_type == 'Permanent':
       createdate = datetime.datetime.now()
       r = table.put_item(
           Item = {
                   'email': bounce_mail,
                   'createdate':str(createdate)
           }
       )
    return r
  except Exception as e:
    print(e)
    raise e    

3.SNSの設定


・トピックを作成します。























・トピックを作成してからサブスクリプションを作成します。














・プロトコルにLambdaを選択してエンドポイントを選択します。














4.DynamoDB作成

[email]項目をキーとしたDynamoDBのテーブルを作成します。
DynamoDBのテーブル作成方法は適当にググれば出てくるので
そちらをご参照ください。

5.SESを使うLambdaの作成

SESからメール送信するLambdaを作成します。

import boto3
import json
import os

SRC_MAIL = os.getenv('FROM_ADDR')
REGION = os.getenv('SES_REGION')
BUCKET = os.getenv('BUCKET_NAME')
BOUNCE_TABLE = os.getenv('TABLE_NAME')

def exist_email(mail_addr):
  """
  Send Mail Check
  """
  try:
      result = 'notexist'
      dynamodb = boto3.resource('dynamodb')
      table    = dynamodb.Table(BOUNCE_TABLE)
      em = table.get_item(
                Key={
                     'email':mail_addr
                }
      )
      if ('Item' in em):
         result = 'exist'
      return result
  except Exception as e:
    print(e)
    raise e

def get_template(file_name):
  """
  S3 get mailbody
  """
  try:
      s3_cl = boto3.client('s3')
      response = s3_cl.get_object(Bucket=BUCKET, Key=file_name)
      template_data = response['Body'].read()
      return template_data    
  except Exception as e:
    print(e)
    raise e

def send_email(source, to, subject, body):
  """
  SES send email
  """
  try:
      client = boto3.client('ses', region_name=REGION)
      response = client.send_email(
          Source=source,
          Destination={
              'ToAddresses': [
                  to,
              ]
          },
          Message={
              'Subject': {
                  'Data': subject,
              },
              'Body': {
                  'Text': {
                      'Data': body,
                  },
              }
          }
      )
      return response
  except Exception as e:
    print(e)
    raise e

def lambda_handler(event, context):
  try:
      data = event["body-json"]
      tomail = data['email']
      fn = data['template'] +'.txt'
      subject = "Test Send Mail"
      template = get_template(fn)
      message = template.decode('utf-8')
      mc = exist_email(tomail)
      if mc == 'notexist':
         r = send_email(SRC_MAIL, tomail, subject, message)
      else:
         r = {
               'statusCode': 500,
               'body': json.dumps('Bounce Mail exist')
         }
      return r
  except Exception as e:
    print(e)
    raise e    


6.Cognitoの設定

・IDプールを作成します。












・新しいIDプールの作成をクリックします












・IDプール名を入力し、[認証されていないID・・・]にチェックを入れます。












・確認画面が表示されるので[許可]をクリックします。














・サンプルコードの箇所で[IdentityPoolId]を確認し[ダッシュボードに移動]をクリックします。










・IAMメニューに移動しロールにて「Cognito_XXXXUnauth_Role」を探してポリシーをアタッチします。












・[APIGatewayInvokeFullAccess]をアタッチします。
*本来はセキュリティを考慮してAPIを限定して設定すべきです。















7.APIGateway設定

通常通りAPIを作成してやります。今回はPOSTで作成しました。
・CORS設定してやります。

























・メソッドリクエストで認可の箇所で[AWS_IAM]を選択します。









・統合リクエストにて「マッピングテンプレート」を設定します。













・「application/JSON」を追加して以下のパラメータを入力、保存します。

{

   "body-json" : $input.json('$')

}

















・フォーマットチェック用にモデルを作成します。
















・モデルのスキーマに以下の様に入力し、作成します。

{
    "title": "sendEmail",
    "type": "object",
    "properties": {
        "email": {
            "type": "string"
        },
        "template": {
            "type": "string"
        }
    },
    "required": ["email", "template"]
}















・メソッドリクエストで「リクエストの検証」で[本文の検証]を選択し、「リクエスト本文」にて「コンテンツタイプ」で[application/JSON]を入力後に[モデル名]で作成したモデルを選択します。













8.フロントプログラムの準備

・APIGatewayでSDKを生成しダウンロードします。







・ダウンロードしたフォルダにフロント用のHTMLを作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>API test</title>
  <!-- AWS SDK -->
  <script src="https://sdk.amazonaws.com/js/aws-sdk-2.7.19.min.js"></script>
  <!-- jQuery -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <!-- API用SDK -->
  <script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>
  <script type="text/javascript" src="lib/url-template/url-template.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>
  <script type="text/javascript" src="apigClient.js"></script>
</head>
<body>
<!-- HTML  -->
<input type="text" name="mail" placeholder="メールアドレスを入力"/><br>
<input type="text" name="templateNO" placeholder="メールテンプレート番号を入力"/><br>
<button id="btn">ボタン</button>
<!-- Other Script -->
<script type="text/javascript">
var apigClient;
$(function(){
  //AWS Credentialの初期化
  AWS.config.region = 'ap-northeast-1';
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
      IdentityPoolId: 'ap-northeast-1:XXXXXXX' //Identity Pool Idを貼り付ける(ARNではない。)
  });
  AWS.config.credentials.get(function(err){
    if(!err){
      //API ClientにaccessKey,secretKey,sessionToken,regionを設定し、初期化します。
      apigClient = apigClientFactory.newClient({
        accessKey: AWS.config.credentials.accessKeyId,
        secretKey: AWS.config.credentials.secretAccessKey,
        sessionToken: AWS.config.credentials.sessionToken,
        region: 'ap-northeast-1'//大事
      });
    }else{
      console.log(err);
    }
  });
});
$('#btn').click(function(){
  var email = $('input[name="mail"]').val();
  var template = $('input[name="templateNO"]').val();
  apigClient.rootPost({},{"email":email, "template":template},{})
  .then(function(result){
    // Add success callback code here.
    console.log(result);
  }).catch( function(result){
    // Add error callback code here.
    console.log(result);
  });
});
</script>
</body>
</html>


9.SESのBOUNCE設定

・SESの「Notifications」から[EditConfiguration]をクリックします。












・Bouncesの箇所でSNSに設定したトピックを選択します。
*必要に応じてCompliantsとDeliveriesも設定します。
















なかなか大変ですが、一度やれば何とか流れは押さえられます。APIGatewayからエラーだろうが何だろうが200を返してくるのでエラー時はきちんと500で返す設定をしたり
Cloudwatchにもメトリクス設定したり、BOUNCE時のメールの取り扱いなど細かい運用周りで決めないといけないことは非常に多そうですね。


◆参照サイト

・SES関連

https://developers.cyberagent.co.jp/blog/archives/24908/
https://encr.jp/blog/posts/20200228_morning/
https://www.yamamanx.com/amazon-sessimple-email-service-lambda/
https://d0.awsstatic.com/webinars/jp/pdf/services/20160106_aws_backbelt-ses-public.pdf

・BOUNCE関連

https://junkbox.wicurio.com/index.php?SES%E3%81%A8SNS%E3%81%A8SQS%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%80%81%E3%83%90%E3%82%A6%E3%83%B3%E3%82%B9%E3%83%A1%E3%83%BC%E3%83%AB%E5%87%A6%E7%90%86%E3%82%92%E8%A1%8C%E3%81%86
https://www.datastadium.co.jp/engineer/e-reports/4615
https://vitalify.jp/blog/2018/03/aws-ses-bouncemail.html
https://qiita.com/Yoshi_T/items/b12c74b6af09df05dff3
https://qiita.com/KeijiYONEDA/items/5e810d1f07392ab51d22
https://bbh.bz/2019/08/20/ses-bounce-setting-cloudwatch/
https://kiraba.jp/ses-bounce-notification-get-from-sns/
https://blog.hypermkt.jp/2020/2020-03-29-aws-ses-email-architecture/
https://www.letitride.jp/entry/2020/06/11/172513
https://blog.shibayu36.org/entry/2015/08/27/101815
http://blog-tech.fukurou-labo.co.jp/2018/12/03/aws/aws-ses-sns%e3%80%80%e3%83%a1%e3%83%bc%e3%83%ab%e3%82%a4%e3%83%99%e3%83%b3%e3%83%88%e3%81%ae%e9%80%9a%e7%9f%a5%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6%ef%bc%88%e5%be%8c%e7%b7%a8%ef%bc%89/

・APIGateway+Lambda+SES

https://poriweb.hatenablog.com/entry/2018/12/14/073000
https://qiita.com/yukidaru/items/5c95a1cf70fcecd79754
https://dev.classmethod.jp/articles/lambda-to-ses/
https://www.blog.danishi.net/2019/07/20/post-1858/
https://h-piiice16.hatenablog.com/entry/2020/02/15/225856
https://qiita.com/kskinaba/items/c692cadbe57121546413

・APIGateway認証

https://qiita.com/Fujimon_fn/items/5197e2e3db6de9a68401
https://www.novelworks.jp/blog/aws/%E3%80%90aws%E3%80%91api-gateway-%EF%BC%8B-lambda-%EF%BC%8B-dynamodb%E3%81%A7%E3%82%B5%E3%83%BC%E3%83%90%E3%83%AC%E3%82%B9%E3%81%AA%E8%AA%8D%E8%A8%BCapi%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9

・APIGatewayパラメータ取得関連

https://dev.classmethod.jp/articles/sugano-013-api-gateway/
https://liginc.co.jp/430297
https://dev.classmethod.jp/articles/lambdac-apigateway-params/
https://confrage.jp/amazon-api-gateway%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9%E3%81%A8%E3%82%AF%E3%82%A8%E3%83%AA%E6%96%87%E5%AD%97%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%AE%E6%B8%A1%E3%81%97%E6%96%B9/
https://tech.mti.co.jp/entry/2017/04/21/180029
https://qiita.com/t20190127/items/f8163aa85c5953fe65ba
https://qiita.com/tamura_CD/items/ca8e531f74ea5b82a5b7
https://qiita.com/Kept1994/items/663b700ddb55cf59b4c9
http://sugilog.hatenablog.com/entry/2016/05/11/135332
http://sndstudy.hatenablog.jp/entry/2017/01/18/012712

・CloudwatchLogs書込み

https://dev.classmethod.jp/articles/run-lambda-via-sns-and-write-log-to-cwl/

コメント

このブログの人気の投稿

証券外務員1種勉強(計算式暗記用メモ)

GASでGoogleDriveのサブフォルダとファイル一覧を出力する

マクロ経済学(IS-LM分析)