小さなエンドウ豆

まだまだいろいろ勉強中

AWS SQS + Lambda + SES でメール送信システムを作る

AWS SQS + Lambda + SES でメール送信システムを作る

Web サービスを運営しているとメールを送信したいタイミングが山程あります。
例えば、ユーザーの新規登録時、定期的に送信するメルマガ用途は多岐に渡ります。

メール送信の仕組みをモノリシックにサービスの一部として組み込むとパフォーマンスが低下してしまう恐れがあります。 そこでどのような設計にすれば悩んでいたところ、SQS をすすめられてため調査してみました。

また SES や SNS としった AWS による通知系のサービスと連携されることで完全マネージドな通知サービスとして構築を目指します。

Amazon Simple Queue Service(SQS)

名前の通りキューイングのサービスです。

キューには 2 種類あり、標準キューと FIFO キューが選べます。
2 つの違いは配信の順序です。
標準キューの配信順序はベストエフォート型で、配信は少なくとも 1 回行われます。
FIFO キューは、その名の通りメッセージが送信される順序のとおりに 1 回のみ確実に処理されるように設計されています。

また SQS にキューイングすることを Lambda のイベントとすることができます。
今回は SQS にイベントを登録し、それをフックにメールを送ります。

SQS を使う理由

SQS を使う理由として以下が挙げられます。

  • キューがあることによって非同期通信が有効になりパフォーマンス向上が見込める
    • 例えば会員登録時にメールを送ることを SQS にまかせて会員登録処理自体は別にすすめることが可能になる
  • キューに登録することによりデータの一貫性が保たれるため信頼性の向上につながる
  • 完全マネージドなサービスのため運用が楽

aws.amazon.com

Amazon Simple Email Service(SES)

クラウドベースの E メールサービスです。

使い方は簡単で、SES から送信されるメールの送信元メールアドレスを有効化したあとに SDK を使ってリクエストするとメールが送信できるようになります。

今回は Lambda からのリクエストを受けてメールを送信する役割を担います。

※ 注意 SES は東京リージョンではサポートされていないため他のリージョンで利用しなければならない。 また Lambda や Lambda のイベントとなるリソースも同じリージョンに存在しなければならない。 https://docs.aws.amazon.com/ses/latest/DeveloperGuide/regions.html#region-receive-email

Amazon Simple Notification Service(SNS

クラウドベースの通知サービスです。
SNS を使うとメールを送ることが可能です。

ただ送信元のアドレスが固定になってしまうため今回使用は見送りました。
(ちなみに no-reply@sns.amazonaws.com からのメールになります。)

SES と SNS では以下のような違いがあります。

違い SES SNS
東京リージョンでの使用 不可
送信元メールアドレス 可変 固定
HTML形式 不可
料金 1000通につき0.10USD 1000通まで無料,10万件あたり2USD

先程も書きましたが、今回の要件を SNS では満たせなかったことや今後の拡張性を考慮して SES を使用します。

実際にやってみる

改めて今回の構成です。

f:id:h-piiice16:20200215205412p:plain
構成

SQS は標準キューを選んで新しく登録します。
SES は先程言ったように送信元メールアドレスを有効化します。

次に Lmabda だが今回 Serverless Framework を使って実装してみます。

Serverless Framework

サーバーレスアプリケーションの構成管理ツールです。
yml 形式で連携するサービスを記述することが出来ます。
またデプロイをコマンドで行うことができるため非常に楽です。

Serverlass Framework に関しては詳しく記しませんが、プロジェクト内にある serverless.yml を以下のように書くと SQS をフックに lambda を呼び出すことができます。

functions:
  hello:
    handler: handler.mail
    events:
      - sqs:
          arn: arn:aws:sqs:ap-south-1:xxxxxx:node_mail

arn には作成したキューのものを記します。 events には他にも API Gateway や DynamoDB など他のリソースを書くこともできます。

Lambda 関数

実装は以下のような感じです。

import { SQSHandler, SQSRecord } from "aws-lambda";
import "source-map-support/register";
import * as AWS from "aws-sdk";
import nodemailer from "nodemailer";

const ses = new AWS.SES({
    accessKeyId: process.env.AWS_KEY_ID,
    secretAccessKey: process.env.AWS_KEY,
    region: "ap-south-1",
    apiVersion: '2010-12-01'
});

const transporter = nodemailer.createTransport({ SES: ses });

export const mail: SQSHandler = async (event: SQSEvent) => {
  const addresses: string[] = event.Records.map((r: SQSRecord) => {
    return r.body;
  });
  const params = {
    from: process.env.SEND_ADDRESS,
    to: addresses,
    subject: "Email Testing",
    html: "<h1>Title</h1>"
  };

  console.log(params);

  try {
    await transporter.sendMail(params)
  } catch (e) {
    console.log(e);
  }
};

SES のインスタンスを作成する際にはシークレットキーを設定する必要がありました。
TS で記述しているため mail という関数や SQS からのイベントの型を指定しています。

なぜ nodemailer を使うのか?

参考にした実装例では nodemailer をいうモジュールで SES のインスタンスをラップしてから使っていました。
その理由が以下のサイトに載っていたため挙げます。
簡単に言うとメール送信時にパラメータの指定の仕方がこちらの API のほうが簡単だそうです。
特に今回は要りませんでしが、ファイル添付する際など SES の標準 API を使うと煩わしいみたいです。

nodemailer.com

実行

SQS でイベントを発行することができます。
本来は EC2 から SQS にイベントを発行するというシチュエーションを想定していますが、GUI ベースでテストも可能です。
メッセージに送信先のメールアドレスを入力し登録するとメールが送られてきました!

f:id:h-piiice16:20200215224212p:plain
メール本文

html も解釈出来ていますね。

まとめ

まだ不十分ではあるがミニマムでメール送信サービスを作ることができた。
残タスクとして、SQS へのイベントの登録や各サービスで起きたエラーのハンドリング、バウンスメールへの対応などを考えなくてはいけません。

SQS や SES は初めて使うサービスだったがフルマネージドもあってか簡単に使うことができました。
実際に Web アプリケーションでメール機能を実装するよりこちらのほうがコストをかけずできると思います。
また運用、保守のコストも同じくかからないと思うのでおすすめです!

ソースコード

github.com