Posted at

SES SMTP インターフェイスで Message-ID が上書きされる問題と対応

More than 3 years have passed since last update.

NOTE: ブログ移行のため、2年前の投稿を再掲しています。

AWS SES でメール送信時、Message-ID ヘッダを上書きするという SES の仕様と、回避策の話。


問題

AWS SES でメール送信するとき、使用する SES インターフィスにかかわらず、送信メールの Message-ID は外部から指定することができない。

ヘッダ に Message-ID を付与したメールを SES に送った場合、SES で生成した文字列で Message-ID ヘッダ が上書きされてしまう。

そのためメールを送信したクライアント側で Message-ID を保存するには、送信処理後に SES が生成した Message-ID を取得しなければならない。


検証

SES を利用するには SDK を通して SES API を叩く方法と、SMTP インターフェイスを通して、SMTP サーバーとして利用する方法がある。

ではまず、SES API を利用した場合に Message-ID が上書きされる様子を確かめてみる。Python 用の SDK である boto を利用してメールを送信してみる。

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""
send_boto.py
============

Sending mail test used boto.
"""

USERNAME = 'FOO'
PASSWORD = 'BAR'

FROM_ADDRESS = 'from@example.com'
TO_ADDRESS = 'to@example.com'

from boto import ses

def main():
""" entry point """

conn = ses.connect_to_region(
'us-east-1',
aws_access_key_id=USERNAME,
aws_secret_access_key=PASSWORD
)

ret = conn.send_email(
FROM_ADDRESS,
'this is subject',
'This is body.',
[TO_ADDRESS]
)

print ret

if __name__ == '__main__':
main()

    // boto の出力

{
u'SendEmailResponse' : {
u'ResponseMetadata' : {
u'RequestId' : u'foobar'
},
u'SendEmailResult' : {
u'MessageId' : u'00000144cd1b9187-48d79e4a-0067-42f1-a167-1b8374b14541-000000'
}
}
}

到着したメールの Message-ID は、

Message-Id: 

SDK の

SendEmailResponse.SendEmailResult.MessageId + '@email.amazonses.com'

となっているのがわかる。

次に、SMTP インターフェイスを利用して Message-ID を指定したメールを送信してみる。

SMTP インターフェイスを利用した場合でも同様に、Message-ID は上書きされている。

Message-Id: 

では、SMTP インターフェイスを利用した時は、どのようにして SES が生成した正しい Message-ID を取得できるだろうか。


対応策

このフォーラムディスカッションによると、SMTP の DATA コマンドへのレスポンスとして、Message-ID の情報を返してくれるようだ。

たとえば、上の SMTP インターフェイスの実行結果は以下のようになる。

# DATA コマンドの戻り

250 Ok 00000144cbe9a784-cf80bdb4-fafd-46ef-b180-ba6bd561df58-000000

smtplib.SMTP.data の戻り値を利用して、パースしてみる。面倒なので 適当に継承、コピペしてみた。

CAUTION: プロダクション環境に使えるレベルのコードじゃないから、このコードのコピペはやめてね

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""
send_smtp.py
============

Sending mail test used SMTP interface.
"""

USERNAME = 'FOO'
PASSWORD = 'BAR'

FROM_ADDRESS = 'from@example.com'
TO_ADDRESS = 'to@example.com'
SMTP_HOST = 'email-smtp.us-east-1.amazonaws.com'

from email.mime.text import MIMEText
import smtplib

def main():
""" entry point """

msg = MIMEText('This is body.')
msg['Subject'] = 'this is subject'
msg['From'] = FROM_ADDRESS
msg['To'] = TO_ADDRESS
msg['Message-ID'] = 'test_message_id@example.com'

server = smtplib.SMTP(SMTP_HOST)
server.set_debuglevel(1)
server.starttls()
server.login(USERNAME, PASSWORD)
server.sendmail(FROM_ADDRESS, TO_ADDRESS, msg.as_string())
server.quit()

if __name__ == '__main__':
main()

SES から帰ってくる Message-ID はホスト部のみなので、うまく整形してやる必要がある。到着したメールの Message-ID を見ると、

Message-Id: 

Message-ID が生成できた。


Outro

smtplib.SMTP.sendmail で レスポンスを拾えなかったので、PyPI で メール送信系のサードパーティライブラリを探して見たけれど、良い物が見つからなかった。他の言語でも smtplib.SMTP.sendmail みたいな簡易インターフェイスの実装は似たようなもんだと思う。

そして、アプリのレイヤで smtplib.SMTP.data とかを叩くべきでは無いと思う。SMTP はそんなに単純じゃないので、そのレイヤのコードがアプリで必要ならモジュールを書くべきで、そこまでするならアプリケーションからメールを送信するときは素直に SDK を利用したほうが良い。

コードの変更が難しい(SMTP を利用しているアプリを SES でも送信できるようにするなど)シチュエーションでも、Facade を一枚挟み、SES では SDK を利用する様に書き換えたほうがリーズナブルな気がする。

また、Facade の実装では、Message-ID をルーチンの呼び元からは入力できないように設計しておく必要がある。つまり Facade 内部で Message-ID は生成して、戻り値などで返す様に実装しなければいけない。


References



  1. Is it possible to get the ses "messageID" using SMTP interface?, accessed Mar 20, 2014