AWS Lambda で SES を通してメール送信するときに送信元を文字化けさせない
送信元に日本語を使うと文字化けが……
AWS Lambda を使って、AWS SES API を介したメール送信をしようと思いました。
import * as AWS from 'aws-sdk'; const params = { Source: 'テスト送信元 <sender@example.com>', // 送信元 Destination: { ToAddresses: [ 'receiver@example.com', ], }, Message: { Subject: { Charset: 'UTF-8', Data: 'Test email', }, Body: { Text: { Charset: 'UTF-8', Data: 'こんにちは、テストです', }, }, }, }; await new AWS.SES().sendEmail(params).promise();
いろいろ省略していますが、おおむねこんな感じでメール送信しました。
(しっかりしたものは https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-send-email-ses/ をご覧ください)
が、しかし……
送信元の名前がめちゃくちゃ文字化けしました。
=?charset?encoding?encoded-text?= の形式で記載する
「encodeURIComponent とか必要なのか〜?」といろいろ試してみても上手くいかなかったのですが、
公式ドキュメントを読んだら、謎を脱する一歩を進められました!
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SES.html#sendEmail-property
Amazon SES does not support the SMTPUTF8 extension, as described in RFC6531. For this reason, the local part of a source email address (the part of the email address that precedes the @ sign) may only contain 7-bit ASCII characters. If the domain part of an address (the part after the @ sign) contains non-ASCII characters, they must be encoded using Punycode, as described in RFC3492. The sender name (also known as the friendly name) may contain non-ASCII characters. These characters must be encoded using MIME encoded-word syntax, as described in RFC 2047. MIME encoded-word syntax uses the following form: =?charset?encoding?encoded-text?=.
RFC 2047 に従って、
=?charset?encoding?encoded-text?=
みたいな書き方をすればいいよ! とのこと。
……と言われても、RFC 2047 のドキュメントが長くてわかりづらいなあ、と思っていたら、
SendGrid のサイトがいい感じに説明してくれていました。
https://sendgrid.kke.co.jp/blog/?p=10958
件名が「こんにちは」という内容を、文字セット「UTF-8」、エンコード方式「Base64」で指定する場合、メールソースのSubjectヘッダは次のような内容になります。
Subject: =?UTF-8?B?44GT44KT44Gr44Gh44Gv?=
(中略)
=?文字セット?エンコード方式?エンコード後の文字列?=
ここで、文字セットには、メール本文と同様「UTF-8」や「ISO-2022-JP」といった値が入り、エンコード方式には、Base64の場合「B」、Quoted-Printableの場合「Q」いずれかの値が入ります。
「なるほど!」と思って、 最初に記したコードの From 部分を
Source: '=?UTF-8?B?44GT44KT44Gr44Gh44Gv?= <sender@example.com>', // 送信元
のようにして送ってみました。
キタァ!
送信元の名前が無事、日本語で届いてくれました。
JavaScript で文字列の BASE64 エンコードをするには
地味にここも難しいです。
https://developer.mozilla.org/ja/docs/Glossary/Base64 で記載されているように、
btoa(unescape(encodeURIComponent('こんにちは'))); => '44GT44KT44Gr44Gh44Gv'
でいけるのですが、
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/unescape に書かれているように
unescape
は基本使うべきでない、とされているのでこれを使っていいかは迷うところです。
で、他の方法もいくらか知りました。
https://qiita.com/i15fujimura1s/items/6fa5d16b1e53f04f3b06 に記載されているような
btoa(String.fromCharCode.apply(null, new TextEncoder().encode('こんにちは'))); => '44GT44KT44Gr44Gh44Gv'
という方法もあります。
また、Lambda を Node.js で動かしているので、
Node.js の Buffer オブジェクトを使って
Buffer.from('こんにちは', 'utf8').toString('base64'); => '44GT44KT44Gr44Gh44Gv'
とする方法もあります。
こうして Unicode 文字列を送信元に設定することが!
以上のことを踏まえて、
=?UTF-8?B?<BASE64エンコードした文字列>?=
のような送信元となるよう、
BASE64エンコードを先におこなうこととしました。
import * as AWS from 'aws-sdk'; const senderNameBase64 = Buffer.from('テスト送信元', 'utf8').toString('base64'); const params = { Source: `=?UTF-8?B?${senderNameBase64}?= <sender@example.com>`, // 送信元 Destination: { ToAddresses: [ 'receiver@example.com', ], }, // 以下略
これで、Lambda から AWS SES を介して送るメール送信元に、
ASCIIコード外の文字列を使うことができるようになりました!!!
調べた結果だけ書いているので短い記事に収まっていますが、
ここまでたどり着くのに数時間かかりました……。
私のように困った誰かの、助けになれましたら幸いです!