SendGridのWebhookをGoogle Cloud Functionsで受けてCloud Datastoreに貯める
やりたいこと
現在開発中のサービスではメール送信にSendGridを利用している 。
ただ、webAPIにメール送信リクエストを送って終わっているので、送信時のエラーはわかるが宛先間違いや受信メールサーバ側でのブロックまで検知できていない。
まずはSendGridのWebhookをサーバレスで貯めるところまで進める。
Node.js8とCloud Datastoreを使った例がなかったので残す。
途中までは以下と同じ
SendGrid のチュートリアル
Cloud Functionsの設定
関数作成
- 名前
-
(例)sendgrid-web-hook
※エンドポイントのURLになるのでわかりやすく - メモリ
- とりあえず128MB
- トリガー
- HTTP
- ソースコード
- インラインエディタ
ブラウザで完結するのでお手軽 - ランタイム
- Node.js 8
※async/awaitがデフォルトで使えるようになっている - リージョン
- asia-northeast1
- タイムアウト
- 60秒
- 環境変数
- Basic認証とCloudstore操作用に以下の3つを設定
- BASIC_USERNAME
- BASIC_PASSWORD
- PROJECT_ID
SendGridの設定
ログインしてダッシュボードへ
「Settings」>「Mail Settings」>「Event Notification」>「HTTP POST URL」
ここにCloud Functionsで作成した関数のエンドポイントを指定する。
以下の記法でBasic認証も使える。
http(s)://username:password@endpoint
ソース
index.js
const Buffer = require('safe-buffer').Buffer; const Datastore = require('@google-cloud/datastore'); const BASIC_USERNAME = process.env.BASIC_USERNAME; const BASIC_PASSWORD = process.env.BASIC_PASSWORD; const PROJECT_ID = process.env.PROJECT_ID; const datastore = Datastore({projectId: PROJECT_ID}); /** * Verify that the webhook request came from sendgrid. * * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg==" */ function verifyWebhook (authorization) { const basicAuth = Buffer.from(authorization.replace('Basic ', ''), 'base64').toString(); const parts = basicAuth.split(':'); if (parts[0] !== BASIC_USERNAME || parts[1] !== BASIC_PASSWORD) { const error = new Error('Invalid credentials'); error.code = 401; throw error; } } /** * Receive a webhook from SendGrid. * * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html * * @param {object} req Cloud Function request context. * @param {object} res Cloud Function response context. */ exports.sendgridWebhook = async (req, res) => { try { if (req.method !== 'POST') { const error = new Error('Only POST requests are accepted'); error.code = 405; throw error; } await verifyWebhook(req.get('authorization') || ''); const events = req.body || []; for (const event of events) { event['sg_message_id_prefix'] = event['sg_message_id'].match(/^(.*?)\.filter.*/)[1]; const key = datastore.key([ 'sg_event', event['sg_event_id'] ]); const entity = { key: key, data: event }; await datastore.insert(entity).catch((err) => console.error(err)); } res.status(200).end() } catch (err) { console.error(err); res.status(err.code || 500).send(err); }; };
package.json
{ "name": "sendgrid-webhook", "version": "0.0.1", "main": "index.js", "dependencies": { "safe-buffer": "^5.1.2", "@google-cloud/datastore": "1.3.4" } }
Datastore内データイメージ
SendGridで定義される変数+『sg_message_id_prefix』を保存する。
『sg_message_id』は【[共通部分].filter[メールアドレスごとに個別部分]】の形になっていて、
サービス側でメール送信APIを叩いたときに取得できるのは共通部分なので、検索しやすいようにカラムをわけておく。
※前方一致で無理矢理とってくることも可能だけどきれいな方法ではなかった
続きは別記事で。
tminami.hatenablog.com