はじめに
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
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を編集
{
"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 に、メール送信フォームを作成
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の設定
/* 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 ファイルを編集します
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から入手する必要があります。
EMAIL_USER=********@gmail.com
EMAIL_PASS=****************
### メールアドレスを環境変数を設定しているので、functionsにdotenvモジュールをインストールする。
cd functions
npm install dotenv
node.jsのバージョンを変更
"node":"22" ⇒ "node":"20"
npm install
アプリのHostingを実施
npm run build
firebase deploy --only hosting
Firebase Functionsのデプロイ:
全てではなく、functionsだけをデプロイします。
firebase deploy --only functions