AWSSESのSMTPを利用するとmessageIDが書き換わる

AWSSESのSMTPを利用してメール送信をすると、messageIDが上書きされてしまう。

そのため、以下のようなシステムは破綻することになる。

  • message_header_id を生成する
  • 生成した message_header_id をDBに保存
  • 返信メールの in_reply_to と突き合わせてどのメールに対する返信か紐づける

messageIDが上書きされてしまうのは避けられないとして、送信前のmessageIDと送信後のmessageIDさえ何かしらの方法で紐づけることができれば、一応、何とかはなるので、紐づける方法を紹介する。

  • SMTP送信の時にSNSトピックにメッセージを送る
  • SNSトピックでLambda関数を購読状態にする
  • lambda関数で送信前と送信後のmessageIDを取得
  • lambda関数でよしなにする(私の場合は、DBの送信前の message_header_id を 送信後の message_header_id に更新するクエリを実行)

さて、肝心のLambda関数の中身は以下である。

event 変数にメールの情報が入ってくるので、関数の通りに取得していくと、送信前と送信後のmessageIDを取得できる。

sns_obj['Message']JSON.parse している理由は、なぜかこの項目は文字列で値が返ってくるからである。

オブジェクトの中身の詳細は、公式ドキュメントを参照。

exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };

    const sns_obj = event['Records'][0]['Sns']
    console.log(sns_obj)
    const message_obj = JSON.parse(sns_obj['Message'])
    console.log(message_obj)
    const mail_obj = message_obj['mail']
    console.log(mail_obj)
    const new_message_id = `<${mail_obj['messageId']}@email.amazonses.com>`
    const original_message_id = mail_obj['headers'].find(x=>x['name']=='Message-ID')['value']

    // result
    console.log(original_message_id)
    console.log(new_message_id)

    return response;
};

Lambda関数が実行されない

Lambdaが実行されない

検証環境でLambdaの実行を確認したし、このロールを本番でも流用しちゃおう、と思って検証用のソースと本番用のソースで同じロールを利用すると、Lambdaが実行されなかった。

原因は、検証用のソースを作った時に作成されたロールに与えられているポリシーは、 AWSLambdaBasicExecutionRole-XXXXX となっており、現在アタッチされているロールは、特定のLambdaしか実行することが許されていない。

そのため、ロールの AWSLambdaBasicExecutionRole-XXXXX というポリシーを削除して AWSLambdaBasicExecutionRole を与えることで、すべてのLambdaが実行できるようになる。

ロールは適切に管理するべきだが、それほどクリティカルなことをしない場合は、これでよいだろう。

lambdaからRDSに書き込みをする

VPCに入れる

lambdaをRDSにリーチできるように、VPC内に入れることで、RDSに対して直接データを書き込めるようになる。

lambdaの「ネットワーク」の設定から設定することができる。

以下のサイトにエンドポイントの作成について参考になると思う。

VPC Endpointを使ってS3にアクセスしてみる – サーバーワークスエンジニアブログ

S3などへのアクセスはエンドポイントを作成

この際、注意が必要なのが、lambdaからS3へのアクセスなどが遮断されてしまうため、VPCエンドポイントを作成して、S3にアクセスできるようにしなければならない。

外部モジュールを入れる際はlayerを追加

lambdaで外部モジュールやパッケージを利用する際は、layerにzip化したパッケージ群をアップロードする必要がある。

「layer aws nodejs」で検索すると方法が出てくると思う。

RDSへの書き込みは通常通りに

nodejsからmysqlにデータを書き込むことを想定する。

var mysql = require('mysql');
var connection = mysql.createConnection({
    host: "<rds_endpoint>",
    user: "<rds_username>",
    password: "<password>",
    database: "<db_name>",
});
// console.log(connection);
exports.handler = (event, context, callback) => {
    connection.query('show tables', function (error, results, fields) {
        if (error) {
            connection.destroy();
            throw error;
        } else {
            // connected!
            console.log(results);
            callback(error, results);
            connection.end(function (err) { callback(err, results);});
        }
    });
};

参考

Querying RDS MySQL DB With NodeJS Lambda Function

mailparserで `iso-2022-jp` を扱う

メールが文字化けをしてしまう

何も考えずに mailparser を利用すると、 iso-2022-jp文字コードの日本語文字が文字化けしてしまう。

これを対処するには、 iconv を利用することで解決することができる。

まず、 iconv をinstallする。

npm i iconv

MailParser を利用している場合は以下の記述に直す。

const Iconv = require('iconv').Iconv;
const MailParser = require('mailparser').MailParser;
let parser = new MailParser({ Iconv });

simpleParser を利用している場合は以下の記述に直す。

const Iconv = require('iconv').Iconv;
const simpleParser = require('mailparser').simpleParser;
simpleParser('rfc822 message', { Iconv }, callback);

なお、 iso-2022-jp を含めてメールの文字化けは、以下のサイトに通すことで、デコードしてくれる。 文字化けが起きていたころはデコードするのに、大変重宝した。

https://www.bungu-do.jp/tools/mail_subject

参考

https://github.com/nodemailer/mailparser/issues/199

nodemailerとさくらのメールサーバからメールを送信

nodemailerからさくらのメールサーバ経由でメールを送信する

普通には送信できない

以下のような587ポートへの接続は失敗する。

port: 587,
secure: true,

メッセージは以下のようなものが表示される。

Error: 47437359817600:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:827: code: 'ECONNECTION', command: 'CONN' }

587を指定しているのに465が強制される

これは、Nodeでさくらのメールを使うを読むまでは全くわからなかった。

そのため、以下のように設定を行う。

const transporter = mail.createTransport({
  host: 'XXX.sakura.ne.jp',
  port: 587,
  secure: false,
  requireTLS: true,
  auth: {
      user: 'username',
      pass: 'password'
  }
});

海外のサービスからアクセスする際は注意

海外からさくらに対してアクセスさせる際は注意しなければならない。 具体的には、私はfirebse functionsのusリージョンからアクセスをさせようとしていたため、エラーが発生した。 メッセージは以下。

Error: Mail command failed: 550 5.7.1

これを回避するには、さくらの国外IPアドレスのフィルタを無効にする必要がありそう。 無効にするにはドキュメントをを参照。 (他人のサーバなのでこれ以上は試していない)

各種メールサービスプロバイダのSMTP設定

各種メールサービスプロバイダのSMTP設定

GmailSMTP設定

送信は安定している。 GmailSMTP利用する際は、同時に最大5件までのメールしか送れなかったので注意。

TLS の場合はポート587を利用する。

host: 'smtp.gmail.com',
port: 465,
user: 'gmailのアカウント名',
pass: 'gmailのパスワード'

AWS SESのSMTP設定

こちらも送信は安定している。 AWS SESは200件に1回くらい、 return mail が返ってきて送信できませんでした、というメールが返ってくるが、 相手にはメールは送られている、ということがあった。 メールの同時送信についてはいまのところ問題なし。

hostは北米バージニアリージョンの場合。 SMTP用のユーザ名とパスワードはSESのGUIコンソールから生成可能。

host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
user: '生成されたアカウント名',
pass: '生成されたパスワード'

SendGridのSMTP設定

SendGridを利用して7件の同時メールを送信すると、全部送信できるときもあったが、1件しか送信できないというケースもあった。 無料枠だから、送信が不安定なのかはわからないが、調べてみた感じ、そういうわけでもなさそう。 これが有料になれば直るという保証もないので使おうかは迷っているところ。 ポートは 25 / 465 / 587 / 2525 が可能とのこと。

host: 'smtp.sendgrid.net',
port: 465,
user: 'アカウントのユーザ名',
pass: 'アカウントのパスワード'

さくらサーバのSMTP設定

メールアドレスの作成を行い、あらかじめメールアドレスを用意しておくこと。

host: '{アカウント名}.sakura.ne.jp',
port: 587,
user: '作成したメールアドレスのユーザ名@{アカウント名}.sakura.ne.jp',
pass: '作成したメールアドレスのパスワード'

参考

https://support.sendgrid.kke.co.jp/hc/ja/articles/204187885-SMTP%E3%81%AE%E6%8E%A5%E7%B6%9A%E6%83%85%E5%A0%B1%E3%82%92%E6%95%99%E3%81%88%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84- https://laraweb.net/tutorial/1265/