ハトネコエ Web がくしゅうちょう

プログラミングやサーバー・Web制作、チームマネジメントなど得た技術のまとめ

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コード外の文字列を使うことができるようになりました!!!

調べた結果だけ書いているので短い記事に収まっていますが、
ここまでたどり着くのに数時間かかりました……。

私のように困った誰かの、助けになれましたら幸いです!