📬

(AWSでメール)SESでapi叩くだけでメール送信

2023/06/26に公開

目次
・目次
・概要
・料金試算
・方法

概要
メールサービスなどを用いてメールを送信する場合、cors対応していないなど、サーバが立てられていない場合送信方法が限られます。
そのようなサーバが立っていない状態でメールを送信したい場合、apiGateWayとLambda, SESのAWSサービスを用いることで安い料金でシンプルにメールを送ることができるのでその方法をまとめる。

料金試算
まずはメールサービスとしてAWSサービスを用いることでどれくらいの計算をすることが必要。
料金資産に用いたのはAWS Pricing Calcualtor。
https://calculator.aws/#/
後にも書くのだがapiを叩くためのapi gateway, 処理を記載するlambda, メールを送信するためのsesの3つのサービスが関わるのでその3つのサービスの料金を試算する。

いずれもサービスリージョンはAsia Pacific(Tokyo)に設定、
api gatewayは月リクエストを千回、lambdaも1000,sesもEメールを1000回/月に設定したところ、以下のように月1ドル程度だったことより、ほぼ料金がかからないことを確認できたのでこの方針を採用決定。
api gateway

lambda

ses

結果


実装
まずはsesから行う。
最初に送信テストを試すだけならば検証済みIDに登録されたメールアドレスにのみメールが送れるようになっており、本番運用をするにはメール送信数の解除およびサンドボックス制限の解除の申請を行わなければいけないのでテストが始まる頃に実施しました。
技術的な課題などで設定が難しいとかはなく、使用予定のドメインと使用用途をできるだけ丁寧に説明したら申請は通ります。通らなかったとしても再申請して申請されれば何の問題もありません。
結果5000件まで月に送信できるようになりました。

次にlambdaの実装を行います。
必要最低限の要件としては、下記よりその実装を行なった。

  • 顧客にメールが送信できること
  • 顧客にメール完了メールをマーケティング担当に送信できること
  • 送られた内容によってメール内容を変更すること

実装はpythonで行いました。ちなみに外部ライブラリをimportするためにはただimportするだけでなく、ローカルとlambda環境の互換性を同じにした上で、zipで固めてupするなどをする必要があります。

boto3を中心としたライブラリをimport


import boto3
from botocore.exceptions import ClientError
import json
from email.header import Header 
import re

共通のheaderや受け取った値の変数などを定義
sesをboto3.clientを使い定義します。

    # headersを外だしの共通変数として宣言
    headers = {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'Access-Control-Allow-Headers, Access-Control-Allow-Origin, Access-Control-Allow-Methods, Origin, X-Requested-With, Content-Type, Accept, Z-Key',
        'Access-Control-Allow-Methods': 'POST, OPTIONS'
    }
    ses = boto3.client('ses', region_name='ap-northeast-1')
    body = json.loads(event['body'])
    family_name = body['familyName']
    given_name = body['givenName']
    name = family_name+given_name
    company = body['company']
    department = body['department']
    phone_number = body['phoneNumber']
    mail_address = body['mailAddress']
    file_name = body['fileName']
    email_kind = 'ebook'

フォームの値などにより送信メール内容を変更するためにダウンロードリンクのjsonと一致したvalueを受け取れるようにします。
lambdaにjsonをあげるときにも階層を間違えずに定義することで読み込むことができます。

    # jsonから該当するダウンロード資料内容とリンク
    with open('download_link.json') as f:
        file_dict = json.load(f)
        file_url, file_title = file_dict[file_name]
        # 導入インタビューの場合file_titleをemail_kindに代入
        if file_name.startswith("interview-"):
            email_kind = file_title

最適現のバリデーションを行った後に、メール本文を顧客と当社マーケティング担当へのメールとの2種類作成します。
メールのインデントや改行などを以下のように調整しながらメールを作成します。

    # メール本文(マーケティング担当用)
    email_body_for_marketing = f'【{email_kind}】ダウンロードがありました。\n' \
             f'-----------以下送信内容---------------------------------------\n' \
             f'【会社名】\n' \
             f'{company}\n' \
             f'【部署名】\n' \
             f'{department}\n' \
	     中略
             f'-----------ここまで 送信内容---------------------------------------\n' \
             f'\n'

送信元、送信先、メール内容などを以下のように定義してtry節でsesを実際に呼び出します。
こちらで注意なのがメールの差出人が文字化けしないようにutf-8でencodeした上で設定する必要があります。

    email_for_marketing = {
        'Source': '%s <%s>'%(Header(sender_name.encode('utf-8'),'utf-8').encode(),'info@xxxxxx.jp'),
        'Destination': {
            'ToAddresses': [consultant_mail_address]
        },
        'Message': {
            'Subject': {'Data': email_for_marketing_subject},
            'Body': {
                'Text': {
                    'Data': email_body_for_marketing
                }
            }
        }
    }
    
    try:
        marketing_mail_response = ses.send_email(
            Source=email_for_marketing['Source'],
            Destination=email_for_marketing['Destination'],
            Message=email_for_marketing['Message']
        )
	中略

ここでお客さまへのメールアドレスさえ200ならresponseを200で返すように設定するようにして、マーケティング担当用メール送信時エラーなど顧客に関係ない処理にてエラーが生じた場合ではエラーが返らないように設定しました。


中略
        # お客様へのメールが送信されればマーケティング担当用のメールリクエストが失敗してもlambdaとしては200を返すようにする
        message = 'Email sent! Message ID is : ' + customer_mail_response['MessageId']
        if marketing_mail_response:
            return {
                'statusCode': 200,
                'headers': headers,
                'body': json.dumps({'message': message})
            }

    except (ClientError) as e:
        message = e.customer_mail_response['Error']['Message']
        if 'customer_mail_response' in locals() and 'MessageId' in customer_mail_response:
            # エラーを取得してきてもお客様へのメールが来ているのならばmessageは正常とする
            message = 'Email sent! Message ID is : ' + customer_mail_response['MessageId']
            return {
                'statusCode': 200,
                'headers': headers,
                'body': json.dumps({'message': message})
            }
        else:
            return {
                'statusCode': 403,
                'headers': headers,
                'body': json.dumps(message)
            }

作成したlambdaを呼び出すためにapi gatewayにて設定します。
corsの設定で苦戦しましたが、jsを用いてpostを叩いて送信することができたら全ての設定完了です。

最後に
メールの内容なども柔軟にでき、apiを叩くだけではcorsに引っかかるメールサービスなどもありますがsesはもちろんそんなことはなく、料金もほぼかからないのでとても良いのではないかと思います!

備考

Discussion