reactでfirebaseを使ってgmailを送信

# はじめに
　reactでfirebaseを使ってgmailを送信するアプリ作成に関する忘備録です。

 ## 条件
・node.jsがインストール済み
・firebaseのプロジェクトがあること（従量加算）

## フロントエンドとバックエンドの同時作成
フロントエンドはhostingにデプロイし、バックエンドはfunctionにデプロイする必要があるようですが、reactでは同時に作成できるようです。

### firebaseの準備
Firebase コンソールで新しいプロジェクトを作成

Firebase コンソールにアクセスし、プロジェクトを作成します。
Firebase Authentication の有効化

コンソールの「Authentication」で、メール/パスワード認証を有効にします。
Firestore データベースの作成

「Firestore Database」を選択し、データベースを作成します。
セキュリティルールを設定（開発中は全て許可でも可）。
Firebase Functions を有効化

「Functions」を選択し、Cloud Functions を有効化します。


### アプリ作成
```:ターミナル
npx create-react-app email-sender
cd email-sender
```
このまま、だとreactのバージョンが合わないので、node_modulesとpackage-lock.jsonを削除する。
### :ターミナル
```
(macの場合)
rm -rf node_modules package-lock.json
(windowaの場合)
Remove-Item -Recurse -Force node_modules
Remove-Item -Force package-lock.json
```
package.jsonを書き換えてreactのバージョンを下げる。
### :ターミナル
```
{
  "name": "email-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "cra-template": "1.2.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
    "devDependencies": {
    "@babel/plugin-proposal-private-property-in-object": "^7.21.11"
  }
}
```
変更したpackage.jsonに基づいて再インストールを実施
```:ターミナル
npm install
```
### 起動するとweb-vitalsがないと表示されるので、インストールする。
```:ターミナル
npm install　web-vitals
```
### axiosも必要なのでインストールする。
```:ターミナル
npm install　axios
```


### Firebase SDKのインストール:
```:ターミナル
npm install firebase
```
### firebaseの初期化

```:ターミナル
firebase init
```
### アプリにfunctiondフォルダができないとき
```:ターミナル
firebase init functions
Are you ready to proceed? (Y/n) 　ｙ
 (*) Functions: Configure a Cloud Functions directory and its files
>( ) App Hosting: Configure an apphosting.yaml file for App Hosting
 (*) Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub  

 > Use an existing project
```

### firebase.jsファイルの作成:
配置先はsrc/firebase.js
```: firebase.js
import { getAnalytics } from "firebase/analytics";
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore"; // Firestoreをインポート
import { connectFunctionsEmulator,getFunctions } from "firebase/functions";

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
};
// Firebaseを初期化
const app = initializeApp(firebaseConfig);

// 各サービスを初期化
const analytics = getAnalytics(app);
const functions = getFunctions(app);
const db = getFirestore(app); // Firestoreの初期化
if (window.location.hostname === "localhost") {
  connectFunctionsEmulator(functions, "localhost", 5001); // エムレーター用の設定
}
export { functions, db, analytics };
```

### シングルページアプリに対応するためのfirebase.jsonを編集
```:firebase.json
{
  "hosting": {
    "public": "build",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "/sendBulkEmail",
        "function": "sendBulkEmail"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": {
    "source": "functions"
  },
  "emulators": {
    "functions": {
      "port": 5001
    },
    "hosting": {
      "port": 5000
    },
    "firestore": {
      "port": 8080
    },
    "auth": {
      "port": 9099
    }
  }
}

```
### メール送信フォームの作成:
src/App.js に、メール送信フォームを作成
```:APP.js
import React, { useState } from "react";
import "./App.css";

// APIのURLを環境に応じて切り替える関数
const getApiUrl = () => {
  if (process.env.NODE_ENV === "development") {
    return "http://127.0.0.1:5001/(ここはアプリのIdです)/us-central1/sendBulkEmail"; // ローカルのURL
  } else {
    return "https://us-central1-(ここはアプリのIdです).cloudfunctions.net/sendBulkEmail"; // 本番のURL
  }
};

function App() {
  const [email, setEmail] = useState("");
  const [subject, setSubject] = useState("");
  const [message, setMessage] = useState("");
  const [responseMessage, setResponseMessage] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();

    const emailData = {
      subject: subject,
      content: message,
      recipients: [email], // 受信者（配列）
    };

    try {
      const response = await fetch(getApiUrl(), {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(emailData),
      });

      if (response.ok) {
        const data = await response.json();
        setResponseMessage(data.message);
      } else {
        const errorData = await response.json();
        setResponseMessage(errorData.message || "Failed to send email");
      }
    } catch (error) {
      console.error("Error during fetch:", error);
      setResponseMessage("Failed to send email");
    }
  };

  return (
    <div className="email-sender">
      <h1 className="email-sender__title">Send Email</h1>
      <form onSubmit={handleSubmit} className="email-sender__form">
        <div className="email-sender__form-group">
          <label className="email-sender__label">Email:</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            className="email-sender__input"
            required
          />
        </div>
        <div className="email-sender__form-group">
          <label className="email-sender__label">Subject:</label>
          <input
            type="text"
            value={subject}
            onChange={(e) => setSubject(e.target.value)}
            className="email-sender__input"
            required
          />
        </div>
        <div className="email-sender__form-group">
          <label className="email-sender__label">Message:</label>
          <textarea
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            className="email-sender__textarea"
            required
          />
        </div>
        <button type="submit" className="email-sender__button">
          Send
        </button>
      </form>

      <div className="email-sender__response-message">
        <p>{responseMessage}</p>
      </div>
    </div>
  );
}

export default App;

```

### App.cssの設定
```:App.css
/* email-senderの全体のスタイリング */
.email-sender {
  font-family: Arial, sans-serif;
  padding: 20px;
  max-width: 600px;
  margin: 0 auto;
  background-color: #f9f9f9;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* タイトルのスタイリング */
.email-sender__title {
  text-align: center;
  font-size: 24px;
  margin-bottom: 20px;
  color: #333;
}

/* フォーム全体のスタイリング */
.email-sender__form {
  display: flex;
  flex-direction: column;
}

/* 各フォームグループのスタイリング */
.email-sender__form-group {
  margin-bottom: 15px;
}

/* ラベルのスタイリング */
.email-sender__label {
  font-size: 14px;
  color: #555;
  margin-bottom: 8px;
}

/* 入力フィールド（input）のスタイリング */
.email-sender__input,
.email-sender__textarea {
  width: 100%;
  padding: 10px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

/* テキストエリアの特別スタイリング */
.email-sender__textarea {
  height: 150px;
  resize: none;
}

/* ボタンのスタイリング */
.email-sender__button {
  background-color: #007bff;
  color: white;
  font-size: 16px;
  padding: 10px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.email-sender__button:hover {
  background-color: #0056b3;
}

/* レスポンスメッセージのスタイリング */
.email-sender__response-message {
  margin-top: 20px;
  text-align: center;
  font-size: 16px;
  color: #333;
}

.email-sender__response-message p {
  color: #ff0000;
  font-weight: bold;
}

```
###　バックエンドのセットアップ
バックエンドは、同一アプリの中のfunctionsフォルダに作成します。
今回の場合、firebase-email-sender/functionフォルダの中です。
まず、Nodemailerをインストールします。
```:ターミナル
cd functions
npm install nodemailer
```
### メール送信用のCloud Functionの作成:
アプリの中に存在するfunctions/index.js ファイルを編集します
```:index.jp
const functions = require("firebase-functions");
const cors = require("cors");
const nodemailer = require("nodemailer");
require("dotenv").config();

// CORSの設定
const corsHandler = cors({ origin: true });

// メール送信のための設定
const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS, // 送信者のGmailパスワード（2段階認証を使っている場合はAppパスワードを使用）
  },
});

// メール送信関数
// メール送信関数
exports.sendBulkEmail = functions.https.onRequest((req, res) => {
  corsHandler(req, res, () => {
    // リクエストメソッドがPOSTでない場合、エラーを返す
    if (req.method !== "POST") {
      return res.status(405).json({ message: "Method Not Allowed" });
    }

    // リクエストボディから送信する情報を取得
    const { subject, content, recipients } = req.body;

    // 送信者のメール情報
    const mailOptions = {
      from: "your-email@gmail.com", // 送信者
      subject: subject, // 件名
      text: content, // メール内容
    };

    // 受信者が配列の場合、順番に送信
    const sendPromises = recipients.map((email) => {
      return new Promise((resolve, reject) => {
        transporter.sendMail({ ...mailOptions, to: email }, (error, info) => {
          if (error) {
            console.error("Email sending failed:", error);
            reject(error);
          } else {
            console.log("Email sent:", info.response);
            resolve(info.response);
          }
        });
      });
    });

    // 全てのメール送信が完了したらレスポンスを返す
    Promise.all(sendPromises)
      .then((results) => {
        res.status(200).json({ message: "Emails sent successfully!" });
      })
      .catch((error) => {
        res.status(500).json({ message: "Error sending emails." });
      });
  });
});

```
###.envファイルの作成
functionsファイルの直下に設ける。
パスワードはアプリ用のものをgoogleから入手する必要があります。
```:.env
EMAIL_USER=********@gmail.com
EMAIL_PASS=****************
```

###　メールアドレスを環境変数を設定しているので、functionsにdotenvモジュールをインストールする。
```:ターミナル
cd functions
npm install dotenv
```
### node.jsのバージョンを変更
``` functions.package.json
    "node":"22"   ⇒　"node":"20"
    npm install
```

### アプリのHostingを実施
```:ターミナル
npm run build
firebase deploy --only hosting
```
### Firebase Functionsのデプロイ:
全てではなく、functionsだけをデプロイします。
```:ターミナル
firebase deploy --only functions
```