Nuxt + Firebase + SendgridのDynamic Templateを使ってメール配信を実装する

## この記事を書いた背景

Firebaseで作成しているWEBアプリにメール配信機能を実装する段階になった。
外部のメール配信サービスで何を使うか比較検討して、SendgridのDynamic Templateを採用することにした。
理由としては、、、
1. 無料配信枠（12,000通 / 月）で当分は足りる
2. 非エンジニアの人が、GUIから文面を変更できるようにしたかった
3. メールの既読管理（開封率、リンククリック etc）をしたかった
4. 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/

## 実現させたい事

1. 非エンジニアの人が、GUIから文面を変更できるようにしたかった
2. メールの既読管理（開封率、リンククリック etc）をしたかった
3. メールの送信タイプが20種類あるので、効率よくcodeを書きたい
4. メールの送信自体は、非同期で構わないのでfunctions側で実行したい

## 悩んだ点

メールの配信プログラムの設計について、かなり時間をかけた。
というのも、メールの配信タイミングとしては、大きく3つが存在する
1. フロント（nuxt）のユーザーアクションがトリガー
2. functions側のonCreateなどのトリガー
3. 毎朝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のファイル

```functions/src/sendEmailOnCall.js
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のファイル

```functions/src/module/sendEmail.js
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は有効だと思う。