Nuxt + nodemailerでメールフォーマットを作る

今回は、`nuxt.js`と`firebase Cloud Function`を用いて簡単なメールフォームを作ってみたいと思います。
`nuxt`と`firebase`の簡単なインストール方法等は[こちら](https://qiita.com/sychocola1/items/6aa185960a9f4e40d3d0)からどうぞ。

#対象者
・`nuxt.js`にまだあまり慣れてはいないが、メールフォームを作りたい人。
・`Laravel`などを使わずに、`nuxt`だけでメール機能を使いたい人。


では早速、`firebase Cloud Functions`を初期化していきましょう。

#firebase Cloud Functionの導入
今回はすでに、`firebase`は入っているものとして考えさせてもらいます。

```ターミナル.
firebase init -functions
```
まずは、`firebase Cloud Functions`を目的のプロジェクトに作成します。

```ターミナル.
? What language would you like to use to write Cloud Functions? (Use arrow keys)

❯ JavaScript 
  TypeScript 
```
`Cloud Functions`に使う言語を聞かれるので、今回は`JavaScript`を選択。

```ターミナル.
? Do you want to use ESLint to catch probable bugs and enforce style? (y/N) 
```
`ESLint`の仕様の有無を聞かれます。
今回は使わないので、`N`を選択。

``` ターミナル.
Do you want to install dependencies with npm now? (Y/n) 
```
依存関係について聞かれます。
ここは後で、めんどくさくなるので`Y`を選択。


これら全てを選択すると、インストールが始まります。

```ターミナル.
✔  Firebase initialization complete!
```
これが表示されると、`firebase Cloud Functions`の初期化に成功しています。

では早速、プロジェクトファイルの中身をみていきましょう。
<img width="1323" alt="スクリーンショット 2020-02-10 18.31.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/3a9f5493-4c6d-6a03-ddc0-035af1e12fa8.png">

実際にプロジェクトの中身を見てみると、確かにプロジェクトディレクトリ直下に、`/functions`ができています。

今回のメール送信機能など、詳しい内容は基本的に`functiuons/index.js`のなかに記述していきます。

#nodemailerとは
node.jsからメールの送信などを可能にするモジュールです。
今回はこれを用いて、管理者に自動でメールを送るような仕様にしていきたいと思います。
公式は[こちら](https://nodemailer.com/about/)からどうぞ。


##nodemailerのインストール

```ターミナル.
cd functions
npm install nodemailer --save
```

これだけで準備はオッケーです。


では早速メール送信機能を実装していきましょう。

##nodemailer実装の下準備
`nodemailer`を実装する前に、送受信用のメールアドレスが必要になります。

```ターミナル.
firebase functions:config:set gmail.email="送信用メールアドレス" gmail.password="送信用メールアドレスのパスワード" admin.email="受信用メールアドレス"
```
これを直接ターミナルに書き込んでおいてください。

念の為、設定できているか確認してみましょう。

```ターミナル.
firebase functions:config:get
```
```
{
  "gmail": {
    "password": "送信用メールアドレスのパスワード",
    "email": "送信用メールアドレス"
  },
  "admin": {
    "email": "受信用メールアドレス"
  }
```
以上の様に表示されていれば、設定は完了しています。

注意点としては、送受信用のメールアドレスは`gmail`が望ましいです。他のものだと、追加料金が発生したり送受信できなかったりする場合があります。
また、受信用のメールアドレスは普段使っているものを使用してもいいですが、送信用のメールアドレスは`firebase Cloud Functions`から干渉を受けるため、セキュリティレベルを下げる必要があります。

##Googleアカウントのセキュリティレベルの変更
`Google`のトップページから、`アカウント`を選択。
<img width="1440" alt="スクリーンショット 2020-02-11 10.15.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/b90b5edf-f37f-b8d0-3898-5651e3b546bd.png">
画面右のドロワー内の`セキュリティ`を選択。
その後、`安全性の低いアプリのアクセス`をオンにしておく。
<img width="1440" alt="スクリーンショット 2020-02-11 10.15.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/d925090b-d614-8836-d3e7-0f9573db3856.png">

これで送信用のメールアドレスの設定は終了です。

では、実際に実装をしていきましょう。

##メール送信機能の実装

```/functions/index.js
const functions = require('firebase-functions');
const nodemailer = require("nodemailer");
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const adminEmail = functions.config().admin.email;

//SMTPサーバーの設定
const mailTransport = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: gmailEmail,
    pass: gmailPassword
  }
});

//メールの内容
const adminContents = function(data){
  return `ホームページにお問い合わせがありました。

お名前：
${data.name}

メールアドレス：
${data.email}

内容：
${data.contents}
`;
};

//実際の機能
exports.sendMail = functions.https.onCall(async function(data, context) {
//送信元、送信先、題名、内容を１つの変数にまとめる
  let adminMail = {
    from: gmailEmail,
    to: adminEmail,
    subject: "ホームページお問い合わせ",
    text: adminContents(data)
  };

//nodemailerのsendMail機能で、メールを送信する。
//変数は送信元などをまとめたもの
  try {
    await mailTransport.sendMail(adminMail);
   } catch (e) {
    return e.message;
   }
});
```

以上で、メール送信機能は完成です。

次にフォームを仕上げていきます。

#メールフォームの実装

今回は`vuetify`を使います。
完成形はこちら。

<img width="1440" alt="スクリーンショット 2020-02-11 10.42.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/08e19d12-3eda-74ee-f66a-53b31e33c4af.png">


```/pages/contact.vue
<template>
    <v-content>
      <v-container>
        <v-col cols="80%">
          <v-card
            title="お問い合わせ"
            textarea="canter"
            class="card-background"
          >
            <v-col cols="100%">
              <v-col cols="80%" >
                <h3 class="card-title">お問い合わせ</h3>
              </v-col>
            <v-col cols="60%">
              <v-form
                ref="form"
                v-model="valid"
                lazy-validation
              >

　　　　　　　　//入力中のみの表示
              <div v-bind:class="{ delatearea: inputdata }">
                <v-text-field
                  v-model="form.name"
                  :rules="nameRules"
                  label="お名前"
                  required
                ></v-text-field>
                <v-text-field
                  v-model="form.email"
                  :rules="emailRules"
                  label="メールアドレス"
                  required
                ></v-text-field>
                <v-textarea
                  v-model="form.contents"
                  outlined
                  auto-grow
                  label="お問い合わせ内容"
                  rows="10"
                  row-height="30"
                  class="contents-area"
                ></v-textarea>
              </div>

　　　　　　　　//確認中のみの表示
              <div v-bind:class="{ delatearea: makesure }">
                <div class="sure-item">
                  <small>お名前</small>
                  <p>{{this.form.name}}</p>
                </div>
                <div class="sure-item">
                  <small>メールアドレス</small>
                  <p>{{this.form.email}}</p>
                </div>
                <div class="sure-item">
                  <small>お問い合わせ内容</small>
                  <p>{{this.form.contents}}</p>
                </div>
              </div>

　　　　　　　　//入力中のみの表示
              <div v-bind:class="{ delatearea: inputdata }">
                <v-btn
                  color="error"
                  class="mr-4"
                  @click="reset"
                >
                  リセット
                </v-btn>
                <v-btn
                  :disabled="!valid"
                  color="success"
                  class="mr-4"
                  @click="validate"
                >
                  確認
                </v-btn>
              </div>

　　　　　　　　//確認中のみの表示
              <div v-bind:class="{ delatearea: makesure }">
                <v-btn
                  color="error"
                  class="mr-4"
                  @click="back"
                >
                  戻る
                </v-btn>
                <v-btn
                  color="success"
                  class="mr-4"
                  @click="sendMail()"
                >
                  送信
                </v-btn>
              </div>

              </v-form>
            </v-col>
          </v-col>
        </v-card>
      </v-col>
    </v-container>
  </v-content>
</template>


<script>
import firebase from "~/plugins/firebase.js";

export default {
  data() {
    return {
      valid: true,
      
      //名前が入力されていないときにエラー文を表示させる
      nameRules: [
        v => !!v || 'お名前を入力してください。',
      ],
      emailerror: false,

      //メールアドレスが入力されていないときにエラー文を表示させる
      emailRules: [
        v => !!v || 'メールアドレスが適切ではありません。',
      ],

      inputdata: false,
      makesure: true,
      form: {
        name: '',
        email: '',
        contents: '',
      }
    }
  },
  methods: {
    validate () {

      //メールアドレスの形を確認する
      if (this.form.email.match(/.+@.+\..+/)) {
        this.inputdata = true
        this.makesure = false
      }else{
        this.emailerror = true;
        this.form.email = "";
      }
    },
    reset () {
      console.log(this.$refs.form.validate())
      this.form.name = "",
      this.form.email = "",
      this.form.contents = ""
      this.$refs.form.reset();
    },
    back () {
      this.inputdata = false;
      this.makesure = true
    },
    sendMail () {
      const tr = this.$router
      const form = this.form

      //ここで、functions/index.jsでexportsした、sendMailを呼ぶ
      const send = firebase.functions().httpsCallable("sendMail");
      send(form)
      .then(function(data) {
        alert("送信しました。");
        tr.push("/");
      })
      .catch(function(error) {
        alert("送信に失敗しました。");
      })
    }
  },
}
</script>


<style>
.card-title {
  color: #9c9797;
  text-align: center;
}
.card-background {
  width: 100%;
  background-color: white;
}
.delatearea {
  position: absolute;
  left: -9999px;
}
.sure-item > p {
  border-bottom: 1px solid #d2d5d2;
}
.contents-area {
  padding-top: 10px !important;
}
</style>

```


少し長くなりましたが、以上でメールのバリデーション・メールの送信ができる環境は整いました。

では実際に動かしていきましょう。

##動作確認
まずは`firebase`にログインしておきましょう。

```ターミナル.
firebase login
```

次に`firebase Cloud Functions`のデプロイを行います。

```ターミナル.
firebase deploy --only functions
```

```
=== Deploying to 'プロジェクト名'...

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (26.72 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: creating Node.js 8 function sendMail(us-central1)...
✔  functions[sendMail(us-central1)]: Successful create operation. 
Function URL (sendMail): https://*****************.cloudfunctions.net/sendMail

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/*********/overview
```
この様に、最初に`deploy`した時だけ`Function URL`が表示されますが、二回目以降は表示されませんので、ご注意ください。

あとは実際に動作を確認してみたください。

```ターミナル.
npm run dev
```

![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/cfaf705b-5baf-c723-90a7-e1adea3188d2.gif)

問題なく送れている様です。


何か間違いがあればご指摘いただけると幸いです。