Amazon SESを使ったメール転送の仕組みの構築と文字化け対策
はじめに
メールインフラの構築は、時に開発者にとって困難に直面するタスクです。その困難は、DNS設定や文字コード、もしくはメールボリュームの管理などがいろいろあります。
本記事では、Amazon Simple Email Service (SES)を利用したメール転送システムの実装と、日本語メールを扱う上での文字コード問題への対処法について記載します。
要件
以下の要件を満たすメール転送の仕組みを構築することでした:
- 高速かつ信頼性の高いメール転送
- 日本語メールの正確な処理
- 費用対効果の良いソリューション
- 管理負荷の少ない小さめのソリューション
そこでClassmethodさんの記事を参考に作ってみました。
技術的な課題と解決策
文字コード問題
日本語メールを処理する際、最大の障壁は文字エンコーディングでした。初期の実装では、文字化けが発生し、実装にてこずりました。
文字コード対応方法
私たちは最終的に、2つのアプローチを比較検討しました:
- Byteからのメッセージ抽出
- Textからのメッセージ抽出
以下は、Pythonのコード例です。
デバッグを何度か試してみた結果、あまり時間を掛けずに実現したかったためこの方法で実装することにしました。
# Byte
# S3からメールデータを取得
logging.info("Retrieving email data from S3...")
s3_client = boto3.client('s3')
s3_object = s3_client.get_object(Bucket=incoming_email_bucket, Key=message_id)
raw_email = s3_object['Body'].read()
# メールのパースとデコード
msg = BytesParser(policy=policy.default).parsebytes(raw_email)
# 日本語などのエンコーディング対応
logging.info("Decoding email data...")
if msg.is_multipart():
body = get_body_from_multipart(msg)
else:
body = msg.get_payload(decode=True).decode(msg.get_content_charset() or 'utf-8', errors='replace')
# Convert body to string
body = str(body)
# Text
# Parse the email body.
mailobject = email.message_from_string(file_dict['file'].decode('utf-8', errors='replace'))
# Create a new subject line.
logging.info("Creating email subject...")
subject_original = mailobject['Subject'] if mailobject['Subject'] else event_msg_subject
subject = "Fwd: " + subject_original
# Create a decoded subject text for body
subject_decoded, encoding = email.header.decode_header(subject_original)[0]
if encoding:
subject_decoded = subject_decoded.decode(encoding)
実装のポイント
Amazon SESの設定
- Lambda用IAMロールの設定
- メール転送ルールの構築
- メール送信先の認証
複雑な最適化よりも、シンプルで安定した転送機能の実現に注力しました。
結果として、単純かつ堅牢なメール転送システムを短期間で構築ができました。
ドメイン横断メール転送の工夫
メールアドレスの@より前の部分が同一な別ドメインのメールアドレスへの転送は、特徴的なものだと思います。例えば、user@example.com
からuser@example.net
への転送を実現しました。
実装では以下ような関数を開発しました:
def get_forward_to(email):
local_part, domain = email.split('@')
domain = domain.split('.')[0]
if local_part in ['target1', 'target2', 'target3']:
mail_address = f"{local_part}@{domain}.net
return mail_address, domain
else:
logger.error(f"対象のメールアドレスではありません: {email}")
return None
実装内容の動作
実装した内容の動きとしては以下のとおりです。
まとめ
昔はメールサーバーの構築に1か月以上かかることもありましたが、今では非常に簡単になりました。久しぶりにメールシステムを構築してみて、昔は文字コードのISO-2022-JPで悩んだことを思い出しました。
Amazon SESを使用したメール転送システムの構築は、サクッとできると思っていました。しかし、S3ファイルの暗号化や文字コードの問題により、予想の3、4倍ほど時間がかかってしまいました。これらの罠を乗り越え、メールの仕組みを整えることができたのは達成感がありました。
技術スタック
- Amazon SES
- Python
- AWS Lambda
- Amazon S3
- UTF-8 エンコーディング
おわりに
Luup では、一緒に開発してくださるソフトウェアエンジニアを積極的に募集しています。
カジュアル面談も実施しておりますのでぜひお気軽にお声掛けください。
Discussion