0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

reactでfirebaseを使ってgmailを送信

Last updated at Posted at 2024-08-22

はじめに

 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)  y
 (*) 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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?