【AWS】SESを使用してメールを送信する(nodejs)

# 要件
検証済みのメールアドレス、もしくは検証済みのドメインでのみメール送受信可能です。あるタイミングでアプリケーションから任意のメールアドレスへメールを送信します。

※今記事のサンプルソースは下記のgithubをご参考ください。
[サンプルソース](https://github.com/ling350181/nodejs-examples/blob/main/src/libs/ses.js)

# 前提
- Route53 で登録しているドメイン名のアドレス (support@xxx.yyy.zzz) をFROMとする
- Node.js環境
- AWSアカウント

# ドメインの認証
## SESのコンソール
1. [Amazon SES コンソール](https://ap-northeast-1.console.aws.amazon.com/ses)を開きます。
2. ナビゲーションペインの [Create identities]をクリックします。
3. Identity typeのDomainを選択します。
4. DomainはRoute53で登録済みのドメインを記入します。
5. [Create identities]ボタンをクリックします。

## nodejs
nodejs経由の場合下記のドキュメントを参考
[ドメイン ID の検証](https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/ses-examples-managing-identities.html)
```js
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region 
AWS.config.update({region: 'REGION'});

// Create the promise and SES service object
var verifyDomainPromise = new AWS.SES({apiVersion: '2010-12-01'}).verifyDomainIdentity({Domain: "DOMAIN_NAME"}).promise();

// Handle promise's fulfilled/rejected states
verifyDomainPromise.then(
  function(data) {
    console.log("Verification Token: " + data.VerificationToken);
  }).catch(
    function(err) {
    console.error(err, err.stack);
  });
```

# 送信元メールアドレスの登録(From)
送信元を登録しないといけません。SESのコンソールからの登録の場合、上記のドメイン登録はほぼ同じやり方です。
nodejs経由の場合は下記のソースをご参考ください。
また、送信先について、sandboxの場合、登録しないと送信できませんが、sandboxを解除したら、Toは自由で登録しなくても送信もできます。
```js
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region 
AWS.config.update({region: 'REGION'});

// Create promise and SES service object
var verifyEmailPromise = new AWS.SES({apiVersion: '2010-12-01'}).verifyEmailIdentity({EmailAddress: "ADDRESS@DOMAIN.EXT"}).promise();

// Handle promise's fulfilled/rejected states
verifyEmailPromise.then(
  function(data) {
    console.log("Email verification initiated");
   }).catch(
    function(err) {
    console.error(err, err.stack);
  });
```
# 送信者名の対応
送信者名を下記のように設定したら、送信者名の設定できます。
~~~
Source: '送信者名 <mail address>'
~~~
ただし、送信者名が日本語の場合は文字化けします。
送る時は送信者名をMIMEエンコードしてSourceパラメータに入れよう。
（メールアドレスまでエンコードはしない事）
sdk: [libmime](https://www.npmjs.com/package/libmime)
サンプルソースコード：
```js
var awsSdk = require('aws-sdk');
var libmime = require('libmime');
AWS.config.update({region: 'ap-northeast-1'})
const ses = new AWS.SES({apiVersion: '2010-12-01'})

exports.handler = (event, context, callback) => {

    // 送信先
    var to = ['送信先メールアドレス'];
    // 送信元
    var from = '送信者メールアドレス';
    // 送信者名
    var fromName = '私がメールを送ったマン';
    
    // 送信者名 MIMEエンコード
    fromName = libmime.encodeWord(fromName, 'Q');

    from = fromName + ' <' + from + '>';

    // 件名
    var subject = 'メールタイトル';

    // 本文
    var body = '本文です。';

    // SES 送信用 パラメータ
    var params = {
        Destination: {
            ToAddresses: [to]
        },
        Message: {
            Body: {
                Text: {
                    Data: body,
                    Charset: 'utf-8'
                }
            },
            Subject: {
                Data: subject,
                Charset: 'utf-8'
            }
        },
        Source: from
    };

    var email = ses.sendEmail(params, function(err, data){
        if(!err) {
            // 成功
            callback(null, data);
        }
    });
};
```

# キャリアメールHTMLの対応
HTMLで送信したい場合、messageのbodyをHtmlにすれば、送信できますが、キャリアメールの場合、Htmlは文字化け発生してしまいます。
```js
   var params = {
        Destination: {
            ToAddresses: [to]
        },
        Message: {
           Body: {
              Html: {
                Data: messageBody,
                Charset: 'utf-8'
              }
            },
            Subject: {
                Data: subject,
                Charset: 'utf-8'
            }
        },
        Source: from
    };

    var email = ses.sendEmail(params, function(err, data){
        if(!err) {
            // 成功
            callback(null, data);
        }
    });
```

こういう場合はマルチパート配信で書き換えます。
## html to text
まずはhtmlのbodyをテキスト書き換えます。html-to-textのSDKを使いと、簡単に変換できます。
```js
    const htmlToText = require('html-to-text')
    // html-to-textを使用してHTMLをテキストに変換
    const plainText = htmlToText.htmlToText(messageBody, {
      wordwrap: true, // 改行する
      ignoreImage: true // 画像を無視
    })
```

## マルチパート配信（sendRawEmail）
sesのsendRawEmail関数を使います。
```js
    // テキストとHTMLメッセージを結合してMultiPartメッセージを構築
    const multiPartMessage = `From: ${fromName} <${from}>\n`
      + `To: ${toAddresses.join(', ')}\n`
      + `Subject: ${title}\n`
      + 'MIME-Version: 1.0\n'
      + 'Content-Type: multipart/alternative;\n'
      + ' boundary="----=_Part_0_1234567890"\n\n'
      + '------=_Part_0_1234567890\n'
      + `Content-Type: text/plain; charset=utf-8\n\n${plainText}\n\n`
      + '------=_Part_0_1234567890\n'
      + `Content-Type: text/html; charset=utf-8\n\n${htmlMessage}\n\n`
      + '------=_Part_0_1234567890--'

    const params = {
      RawMessage: {
        Data: multiPartMessage
      },
      Destinations: toAddresses,
      Source: `${fromName} <${from}>\n`
    }
    return new Promise(function (resolve, reject) {
      ses.sendRawEmail(params, function (err, data) {
        if (err) {
          reject(err)
        } if (data) {
          resolve(data)
        }
      })
    })
```
# 上限緩和申請
- Amazon SES サンドボックス
詐欺行為や不正行為防止及びISPからのSPAM認定回避のため、新規ユーザーのアカウントは、サンドボックス（検証目的の環境）に配置され、動作の一部に制限を受けます。
送信できるのは1日あたり200通まで、1秒あたり1メッセージまで。
アカウントをサンドボックスの外に移動する手続きを行うと、制限が解除された環境で動作します。
手続きには 1 営業日かかるそうです。
# 参考
- [AWS Javascript SDK SESリファレンス](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SES.html)
- [ドキュメント](https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/ses-examples-managing-identities.html)