この記事を書いた背景
Firebaseで作成しているWEBアプリにメール配信機能を実装する段階になった。
外部のメール配信サービスで何を使うか比較検討して、SendgridのDynamic Templateを採用することにした。
理由としては、、、
- 無料配信枠(12,000通 / 月)で当分は足りる
- 非エンジニアの人が、GUIから文面を変更できるようにしたかった
- メールの既読管理(開封率、リンククリック etc)をしたかった
- Sendgrid APIの利用経験がある
SendgridのDynamic Templateに関しては、下記の記事が分かりやすい。
https://sendgrid.kke.co.jp/docs/Tutorials/A_Transaction_Mail/using_dynamic_templates.html
https://dev.classmethod.jp/articles/sendgrid-dynamic-templates/
実現させたい事
- 非エンジニアの人が、GUIから文面を変更できるようにしたかった
- メールの既読管理(開封率、リンククリック etc)をしたかった
- メールの送信タイプが20種類あるので、効率よくcodeを書きたい
- メールの送信自体は、非同期で構わないのでfunctions側で実行したい
悩んだ点
メールの配信プログラムの設計について、かなり時間をかけた。
というのも、メールの配信タイミングとしては、大きく3つが存在する
- フロント(nuxt)のユーザーアクションがトリガー
- functions側のonCreateなどのトリガー
- 毎朝9時に実行などの、Cron的なトリガー
3つを包括する方法として、functions側のmoduleにて、sendGridへの送信を記述。
各トリガーからは、payloadとして、各種IDとtemplateID(Dynamic Template)を送るだけにした。
例として、ユーザーが他の人の写真に、コメントをした場合
1. ユーザーがコメントしたタイミングで、nuxtからonCallで、functions(e.g userCreate ComentOnCall.js)を呼び出す
2. userCreateComentOnCall.js では、functions/module/sendEmail.jsをimportする
3. functions/module/sendEmail.js にて、SendGridのAPIを利用してメールを送信する
2のファイル
const functions = require("firebase-functions");
const sendEmail = require("./module/sendEmail");
module.exports = functions.region("asia-northeast1").https.onCall(
sentryWrapper(async (data) => {
await sendEmail(data);
return { status: "OK" };
})
);
3のファイル
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const sgMail = require("@sendgrid/mail");
const API_KEY = functions.config().sendgrid.key;
const FROM_EMAIL = "SampleApptest@gmail.com";
module.exports = async function(obj) {
const userInfo = await admin
.firestore()
.doc(`userInfos/${obj.userId}`)
.get()
.then((doc) => {
return doc.data();
});
// pattern毎に代入
let templateId = "";
let subject = "";
// 開発モードは、タイトルに環境名を追記
if (functions.config().functions.env !== "production") {
subject += `<${functions.config().functions.env}>`;
}
switch (obj.type) {
case 1: {
subject += "ユーザー登録完了";
templateId = "d-0db91b92c70142cfXXXXXXXXXXXXX";
break;
}
case 2: {
subject += `変更申請 - SampleApp - `;
templateId = "d-82e84fd314fa410fXXXXXXXXXXXXX";
break;
}
case 3: {
subject += `変更承認 - SampleApp - `;
templateId = "d-f17e889213464af8XXXXXXXXXXXXX";
break;
}
case 4: {
subject += `新着メッセージ - SampleApp -`;
templateId = "d-6ccd61a728c548XXXXXXXXXXXXX";
break;
}
}
const links = {
login: functions.config().basic.base_url + "login",
qa: functions.config().basic.base_url + "helps/qa",
cancel: functions.config().basic.base_url + "helps/cancel-policy",
mypage:
functions.config().basic.base_url +
`users/${parentInfo.id}`,
};
let address = await admin
.auth()
.getUser(userInfo.id)
.then((userRecord) => {
return userRecord.email;
});
const payload = {
email: {
templateId: templateId,
subject: subject,
address: address,
},
user: {
id: userInfo.id,
name: `${userInfo.name.last} ${userInfo.name.first}`,
},
links: links,
};
const msg = {
from: FROM_EMAIL,
to: address,
templateId: templateId,
dynamic_template_data: payload,
};
sgMail.setApiKey(API_KEY);
try {
await sgMail.send(msg);
return { status: "OK" };
} catch (err) {
return { status: "NG", text: err };
}
};
上記ファイル内の、template-idである、d-xxxxxxxxxx というのが、Sendgridで作成したDynamic TemaplateのIDになる。
また、SendgridのAPI keyは、事前にfunctions.config().set より、登録しておく必要がある。
今回は、セキュリティーを考慮して、emailのアドレスはAuthenticationから取得。firestoreに保存しても問題はないと思うけど、念のために。
SendgridのDynamic Templateの書き方に関して
Dynamic Templateでは、メールテンプレートの作成方法として「Design Editor」と「Code Editor」の2種類が存在する。
どちらも、mustache記法という書き方で、動的に変数を表示できる。
mustache記法の書き方に関しては、下記を参考に。
https://qiita.com/sengok/items/1d958348215647a5eaf0
今後の課題
今回、メールの送信機能をfunctions側で共通化したことで、Code自体は綺麗に収まったのだが、構築段階での確認作業は大変。というのも、OnCreateや、Authenticationと連携したメソッドを試すには、Localのemulatorでは無理なので、毎回、firebaseにdeployする必要がある。反映までに、数分かかる。。
functionsのデプロイ時間を短縮する方法があったら、知りたい。。。
最後に。。。
今回の非エンジニアの方は、メールの文面をGUIから変更したいというニーズは、どこの会社もあると思う。
そういった観点からは、SendgridのDynamic Templateは有効だと思う。