LoginSignup
8
4

More than 3 years have passed since last update.

Cloud PubSubとSendGridを使ってメール送信機能を切り離す

Posted at

PrAha Inc.のCEO兼エンジニアのdowannaです

サービス内でなんらかのアクションがあった時にユーザにメール通知したい時、ありますよね。
でもサーバにメール送信まで任せてしまうと、どこまでサーバに対して負荷がかかるか心配です。

そんな時はPubSubなどのキューイングサービスを使って、メール送信機能を本体から切り離したいもの。
今回の記事はPubSubとSendGridを使ったメール送信機能のマイクロサービスを作ってみます。

やりたい事

  • 送信予定のメールをPubSubのmessageとしてキューしておく
  • 1日1回、PubSubからmessageをpullしてメール送信
  • メール送信が完了したらmessageを削除

大まかな作業の流れ

  • Topic, Subscriptionを作成
  • Topicに向けてmessageを配信
  • SubscriberClientを作成して、messageを受信
  • messageを使ってメールをsendgridから送信
  • 送信を確認次第、messageをacknowledgeしてPubsubから削除

Topicに向けてmessageを配信

まずはPubSubにTopicを作成する必要がある。

Topic作成方法1:GCPコンソール

コンソールを開き、Pub/Subを選択して、画面上の「トピックを作成」から作成する

Topic作成方法2:CLIから以下コマンドを実行

ローカルで認証済みのCLI、もしくはGCPコンソールから「クラウドシェル」を起動して、Topic作成

gcloud pubsub topics create my-topic

SDK使ってTopicを作る事も可能だけど、ひとまずこの2つを知っておけば困らないのではなかろうか。

Topicにmessageを飛ばす

import { PubSub } from '@google-cloud/pubsub'

const pubsub = new PubSub({ projectId: 'some-project-id' })
const topic = await pubsub.topic('my-topic')
await topic.publishJSON({ text: 'hello!' })

これだけで、先ほど作成した my-topic にメッセージが飛ぶ。

例えばユーザが誰かに「いいね」を送った時にメール通知したいのであれば、いいね直後にサーバからtopicにpublishすれば良い。
メール送信の負荷とか、送信の成否とか一切気にする事無く、ただPubSubに丸投げすれば良いので、
システムの依存性や関心の範囲を少し下げられる。

今回はメール送信に使う本文 'hello!' をPubSubに貯めてみた。

1日1回、PubSubからmessageをpullしてメール送信

まずはコンソールでmessageをpullしてみる

まずメッセージを取得するためには、Topicだけではダメ。
Subscriptionも作成しなければいけない。
CLIで、こんなコマンドを叩いて
topic: my-topic に
subscription: my-sub を追加する

gcloud pubsub subscriptions create --topic my-topic my-sub

これでmessageをpull出来るようになった。
試しにさっきpublishJSONしたmessageを確認してみよう。

コンソール画面から、my-topicを選択して「メッセージを表示」をクリックする

image.png

先ほど作成したsubscriptionを選択する

image.png

そしてPULLをクリックする

image.png

先ほど送信したmessageが取得できる事が確認した。
あとは今のコンソールと同じことを、コードからクライアントライブラリを使って実装してみよう。

クライアントライブラリを使ってmessageをpullしてみる

pullの方法は大きく分けて非同期/同期pullが用意されている。
今回は「すでに登録されているmessageをまとめて取得したい」ので、
公式サイトに載ってるsynchronous pullを使ってみる。

まずはv1パッケージをimportしてSubscriberClientを初期化する

  import { v1 } from '@google-cloud/pubsub'
  const subClient = new v1.SubscriberClient()

続いてclientに「どのSubscriptionからmessageを取得するか、および取得上限数」を定義する。

  const formattedSubscription = subClient.subscriptionPath(
    projectId, // 今回なら some-project-id
    subscriptionName // 今回なら my-sub
  );

  const request = {
    subscription: formattedSubscription,
    maxMessages: 10, // とりあえず上限10通
  };

最後にpullする。これでコンソールで実行したのと同じ事が実装できた。

  const [response] = await subClient.pull(request);

ちなみにこの時点では、まだmessageは削除していない。

メール送信が完了したらmessageを削除

ここから先は各々の実装により異なるけど、例えばsendGridを使っている場合を想定すると、こんな感じ。

    const ackIds = []

    for (const receivedMessage of response.receivedMessages) {
      SendGrid.send({
        to: 'hogehoge@hogemail.com',
        from: 'fugafuga@hogemail.com',
        subject: 'hello world',
        text: receivedMessage.message.data.toString() // ByteなのでStringに変換しておく
      })

      // 失敗した場合の処理はここでは考えていない
      ackIds.push(receivedMessage.ackId) // あとで削除するためにAcknowledgeIdを保管しておく
    }

実務であれば「メール送信に失敗した場合、どうする?message消しちゃって大丈夫?」と検討するけれど、ここでは一旦気にせずackIdsにpushしちゃおう。

全てのメール送信タスクを投げるのに完了したら、これでmessageを一括消去する

    const ackRequest = {
      subscription: formattedSubscription,
      ackIds: ackIds
    }
    await subClient.acknowledge(ackRequest)

acknowledgeされたmessageはPubSubから消える。

以上でPubSubを使ったメール配信の実装が完了した。

まとめ

  • Topic, Subscriptionを作成
  • Topicに向けてmessageを配信
  • SubscriberClientを作成して、messageを受信
  • messageを使ってメールを送信
  • 送信を確認次第、messageをacknowledgeしてPubsubから削除
8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4