Python on AWS LambdaからAmazon SES API経由でメール送信時に日本語の送信者名を表示する

メール送信時、送信者名に日本語を表示したいケースがあった。調べてみると、SMTP経由でメールを送る時の日本語対応方法は色々あったが、Amazon SES API経由の場合はパッと分からなかったのでまとめておく。

 

 

[Amazon SESとは]

本題に入る前に、Amazon Simple Email Service、略してAmazon SESについて簡単に説明しておく。……と言っても公式サイトの引用程度だが。

aws.amazon.com

  • 企業や開発者のための、フレキシブルで可用性が高く、手頃な価格の E メール送受信サービス
  • SMTP インターフェイスAWS SDK を使用してメールを送受信できる
  • EC2インスタンスからメールを送る場合、毎月62,000通まで無料

 

ちなみに、今回のようにAWS Lambdaからメール送信する場合について、RedditのスレッドによればEC2と同じく無料枠があるようだ。

www.reddit.com

I send emails with SES from lambda, calling it from there counts towards your 62k free so no additional costs, but like the other guy said you do pay for lambda compute

 

なお、SESを利用する場合、日本国内の携帯キャリアメールへの送信の際はあまり相性がよくないとソリューションアーキテクトからも指摘されている。

www.slideshare.net

 

SESから携帯キャリアメールへのメール送信については、いくつか対策方法が公開されているが、今回はとりあえずGmail宛にメールを送信できれば良いのであまり気にしないことにする。

dev.classmethod.jp

qiita.com

 

[Serverless Frameworkを利用してPythonプロジェクトを作成する]

ローカルでPythonを実行しても良いのだが、Pythonのライブラリ管理にあまり詳しくなく、依存関係の管理とか複雑なことはやりたくない。なので、AWS Lambda上でPythonを動作させる。つい先日(2019年11月18日)AWS LAmbdaでPython 3.8がサポートされたので、こちらを利用する。

AWS Lambda now supports Python 3.8

 

AWS Lambdaへの関数のデプロイにはserverless framework 1.58.0を利用する。

AWS LambdaのPython3.8テンプレートを使いたかったので、プルリクを投げたらすぐにマージしてもらえた。2019年11月25日現在最新の1.58.0から利用可能となっている。

github.com

 

プロジェクト用のディレクトリを作成して、aws-python3テンプレートからプロジェクトを作成する。

$ mkdir amazon-ses
$ cd amazon-ses
$ sls create -t aws-python3
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.58.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

 

serverless.ymlの内容は次のように変更しておく。

service: amazon-ses

provider:
  name: aws
  runtime: python3.8
  # serverless frameworkはデフォルトのメモリサイズが1024MBなので小さくしておく
  memorySize: 128 
  # 東京リージョンを利用する
  region: ap-northeast-1
  # Amazon SESのAPIを利用できるようにIAMロールの権限を作成する
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "ses:SendEmail"
      Resource: "*"

functions:
  send_mail:
    handler: handler.send_mail
    environment:
      # 事前にセットアップしたSESのリージョンをデプロイ時に指定する
      SES_REGION: ${opt:ses_region, 'us-west-2'}
      # 事前にSESに登録したメールアドレスをデプロイ時に指定する
      SES_FROM_ADDR: ${opt:from_addr}
      # 任意の送信者名をデプロイ時に指定する
      SES_FROM_NAME: ${opt:from_name}

 

handler.pyについては、公式ドキュメントを参考に実装する。

docs.aws.amazon.com

 

サンプルコードの概要としては以下の通り。

import boto3

SENDER = "Sender Name <sender@example.com>"
RECIPIENT = "recipient@example.com"
AWS_REGION = "us-west-2"

SUBJECT = "Amazon SES Test (SDK for Python)"
BODY_TEXT = "This email was sent with Amazon SES using AWS SDK for Python (Boto)."

CHARSET = "UTF-8"

client = boto3.client('ses', region_name=AWS_REGION)

def send_mail:
    # エラーハンドリングは無視……
    client.send_email(
        Destination={
            'ToAddresses': [
                RECIPIENT,
            ],
        },
        Message={
            'Body': {
                'Text': {
                    'Charset': CHARSET,
                    'Data': BODY_TEXT,
                },
            },
            'Subject': {
                'Charset': CHARSET,
                'Data': SUBJECT,
            },
        },
        Source=SENDER,
    )

 

次のようなコマンドでLambda関数デプロイすることができる。

$ sls deploy --from_addr myname@example.com --from_name システム管理者

デプロイ後、マネジメントコンソールから環境変数の設定を確認すると、次のように日本語の送信者名が登録できている。

f:id:kidani_a:20191126031047p:plain

 

あとはsls invoke -f send_mailコマンドを実行すると、メールを送信するLambda関数を呼ぶことができる。

 

[送信者名に日本語を表示する]

いよいよ本題。

先ほどのサンプルコードでSENDERの部分に色々試してみたもののどれもうまく行かず。

SENDER = '"システム管理者" <myname@example.com>'

f:id:kidani_a:20191126015423p:plain

 

SENDER = str('"システム管理者" <myname@example.com>'.encode('iso-2022-jp'))

f:id:kidani_a:20191126015940p:plain

 

from email.header import Header
SENDER = u"%s <%s>" % (str(Header('システム管理者', 'UTF-8')), 'myname@example.com')

f:id:kidani_a:20191126015423p:plain

 

で、色々迷った挙句見つけたのがこのライブラリ。

github.com

FlaskというPythonのWebアプリフレームワーク用の、メール送信ライブラリのようだ。

 

sanitize_addressというメソッドが参考になりそうなので、関連する部分を抜き出してみる。

from email.header import Header
from email.utils import formataddr

def sanitize_address(addr, encoding='utf-8'):
    if isinstance(addr, string_types):
        addr = parseaddr(force_text(addr))
    nm, addr = addr

    try:
        nm = Header(nm, encoding).encode()
    except UnicodeEncodeError:
        nm = Header(nm, 'utf-8').encode()
    try:
        addr.encode('ascii')
    except UnicodeEncodeError:  # IDN
        if '@' in addr:
            localpart, domain = addr.split('@', 1)
            localpart = str(Header(localpart, encoding))
            domain = domain.encode('idna').decode('ascii')
            addr = '@'.join([localpart, domain])
        else:
            addr = Header(addr, encoding).encode()
    return formataddr((nm, addr))

 

いらなそうな部分を色々と削って、最終的には以下の形にすることで、送信者名の表示を日本語にすることができた。

from email.header import Header
from email.utils import formataddr

SENDER = formataddr((Header('システム管理者', 'ISO-2022-JP').encode(), 'myname@example.com'))

f:id:kidani_a:20191126025838p:plain

 

唯一の懸念としては、email.headerについてはレガシーなAPIであると公式ドキュメントに記載されている点。きっと別のやり方があるのだろうけれど、そこまでは調べきることができなかった。

docs.python.org

 

[まとめ]

  • Amazon SESは条件によっては無料で毎月6万通のメールを送受信できるサービス
  • SMTP以外にも、AWS SDKを利用してHTTPでメールを送信できる
  • AWS SDK for Python (boto3)とemail.header.Header/email.utils.formataddrの組み合わせで送信者名を日本語表示できる
  • ただし、上記のやり方はレガシーAPIを利用しているのでPythonのバージョンアップに伴い使えなくなる可能性がある
  • Python 3.8 on AWS Lambda + Amazon SESのサンプルコードはGitHubにまとめた

github.com