😂

Nextjs・Firebase・Nodemailerでお問い合わせフォームを作る

2023/09/17に公開

はじめに

Nextjs と Firebase を使って作成した Web サイトにお問い合わせさフォームを作成していたのですが、Nextjs Api Routes を使っていて実現するのにハマってしまったのでハンズオン形式で実現の方法をまとめます

ここでのハンズオンの内容は以下のリポジトリにあります

https://github.com/jinwatanabe/nextjs-firebase-nodemailer-handson

学べること

Cloud Functions for Firebase を使った Nodemailer のデプロイ

環境

  • VSCode
  • Firebase tools 12.4.7

Firebase への Nextjs のデプロイ、GCP のプロジェクト用意は各自行ってください(firebase login も含む)
npx などの nextjs 作成に関わるものもインストール済みとして進めます

1. Nextjs のプロジェクト準備

まずは問い合わせフォームを作成します
ここでは特にバリデーションなどは行わず、入力された内容をそのまま送信するだけのフォームを作成します

$ npx create-next-app contact-form
> Typescript Yes
> ESLint Yes
> Tailwind Yes
> src/ directory No
> App Router Yes
> import alias No

$ cd contact-form
// VSCodeで開く

2. フォームの作成

Tailwind を使ってそれっぽくフォームを作成します
useStateなどは説明しませんのでわからない方はググってください

app/page.tsx
"use client";
import { SyntheticEvent, useState } from "react";

export default function Home() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");

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

    // ここでメール送信を行う
  };

  return (
    <div className="min-h-screen flex justify-center items-center bg-gray-100">
      <div className="bg-white p-8 rounded-lg shadow-md w-96">
        <h2 className="text-2xl mb-4">お問い合わせ</h2>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <label className="block text-sm font-medium text-gray-600">
              名前
            </label>
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-600">
              メールアドレス
            </label>
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-600">
              内容
            </label>
            <textarea
              value={message}
              onChange={(e) => setMessage(e.target.value)}
              className="mt-1 p-2 w-full border rounded-md h-32"
              required
            />
          </div>
          <button
            type="submit"
            className="w-full p-2 text-white bg-blue-500 hover:bg-blue-600 rounded-md"
          >
            送信
          </button>
        </form>
      </div>
    </div>
  );
}

それでは起動して画面を確かめます

$ npm run dev

http://localhost:3000にアクセスします

フォームが表示されました

2. Nodemailer でメール送信をできるようにする

次は Nodemailer を使って、問い合わせ内容を自分宛てにメール送信できるようにします

ここでは Gmail を利用してメール送信を行います

2-1. 注意

ここで私は Nextjs api routes を利用して/app/api にメール送信用のエンドポイントを作成していました

しかし、これでfirebase deployしてもうまくエンドポイントが利用できませんでした

なので今回はcloud functions for firebaseを利用してエンドポイントを作成します

この機能を利用するにはBlazeプランにする必要があります
https://cloud-ace.jp/column/detail175/

無料枠で行うことはできないので注意してください

2-2. Cloud Functions の作成

作成したcontract-formの上の階層に戻ってもう一つプロジェクトを作成します

$ cd ..
$ mkdir contact-mailer
$ cd contract-mailer-functions
$ firebase init functions
> メールアドレス、プロジェクトは用意したものを選択
> Typescript Yes
> Lint Yes
> npm now Yes
cd functions
// vscodeで開く

これで Fireabse を利用して簡単に関数をデプロイできます

2-3. Nodemailer のインストール

nodemailer を利用するのでインストールを行います

$ npm install nodemailer cors
$ npm install --save-dev @types/nodemailer

CORS の対応も必要になるので一緒に入れました

2-4. アプリからのアクセス許可

Gmail でアプリからのアクセスを許可する必要があります
利用する Gmail のアカウントで以下の設定を行ってください

メニューから「アカウント」をクリック

左メニューから「セキュリティ」をクリック
ここで「2 段階認証」が有効になっているか確認します
なっていない場合は有効に設定しましょう(この例ではなっていません)

認証が済んだら、一番下の「アプリパスワード」の>をクリック

アプリ名を適当に入れて「作成」をクリック

パスワードが画面に表示されるのでメモっておきましょう

2-5. メール送信用の関数を作成

メール送信用の関数を用意します
わからないところはドキュメントをみてくださればと思います

https://nodemailer.com/

まずは環境変数に先程のアプリパスワードとメールアドレスを設定していきます

$ touch .env
GMAILUSER=メールアドレス
GMAILPASSWORD=アプリパスワード

次に関数を実装していきます

src/index.ts
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import * as nodemailer from "nodemailer";
import * as cors from "cors";

// eslint-disable-next-line object-curly-spacing
const corsHandler = cors({ origin: true });

admin.initializeApp();

const transporter = nodemailer.createTransport({
  host: "smtp.gmail.com",
  port: 587,
  auth: {
    user: process.env.GMAILUSER,
    pass: process.env.GMAILPASSWORD,
  },
});

exports.sendNodeMail = functions.https.onRequest(
  (req: functions.Request, res: functions.Response) => {
    corsHandler(req, res, () => {
      const body = req.body;
      const toHostMailData = {
        from: body.email,
        to: "あなたのメールアドレス",
        subject: `【お問い合わせ】${body.name}様からのお問い合わせ`,
        text: `${body.message}`,
        html: `
      <p>お問い合わせ内容</p>
      <p>お名前:${body.name}</p>
      <p>メールアドレス:${body.email}</p>
      <p>お問い合わせ内容:${body.content}</p>
      `,
      };

      return transporter.sendMail(toHostMailData, (error, info) => {
        if (error) {
          console.log(error);
          res.send("error");
        } else {
          console.log("Message sent: " + info.response);
          res.send("success");
        }
      });
    });
  }
);

メールアドレスの箇所は自分のメールアドレスに変更してください
ポイントは最後にres.sendを行っていることです。このあとフロント側でレスポンスに応じてアラートを実行するのですが、この処理がないと永遠に待ち状態になってしまいます
// eslint-disable-next-line object-curly-spacing
また functions はデフォルトではus-central1のリージョンにデプロイを行います。functions.region('asia-east1')のようにリージョンを指定することもできます

2-6. デプロイ

contract-mailer-functionの階層に戻ってコマンドを実行します

$ firebase deploy

デプロイが完了されたらGCPのコンソールからCloud Functionsを開きます

sendNodeMailをクリックして「トリガー」から「トリガー URL」をコピーします

2-7. フロント側の実装

contact-formに戻ってメール送信を実行するように変更します

app/page.tsx
"use client";
import { SyntheticEvent, useState } from "react";

export default function Home() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");

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

    fetch("あなたのsendNodeMailのURL", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name: name,
        email: email,
        content: message,
      }),
    }).then((res) => {
      if (res.status === 200) {
        alert("送信が完了しました。");
        setName("");
        setEmail("");
        setMessage("");
      } else {
        alert("送信に失敗しました。");
      }
    });
  };

  return (
    <div className="min-h-screen flex justify-center items-center bg-gray-100">
      <div className="bg-white p-8 rounded-lg shadow-md w-96">
        <h2 className="text-2xl mb-4">お問い合わせ</h2>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <label className="block text-sm font-medium text-gray-600">
              名前
            </label>
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-600">
              メールアドレス
            </label>
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-600">
              内容
            </label>
            <textarea
              value={message}
              onChange={(e) => setMessage(e.target.value)}
              className="mt-1 p-2 w-full border rounded-md h-32"
              required
            />
          </div>
          <button
            type="submit"
            className="w-full p-2 text-white bg-blue-500 hover:bg-blue-600 rounded-md"
          >
            送信
          </button>
        </form>
      </div>
    </div>
  );
}

handleSubmitにメール送信の処理を追加しました
あなたの sendNodeMail の URL の部分は先程コピーした URL に変更してください

2-8. 動作確認

実際に画面から送信してみます
すべての項目をうめて送信ボタンを押します

すると、アプリパスワードを設定したアカウントにメールが届いているはずです

おわりに

今回は Nextjs と Firebase を使って問い合わせフォームを作成しました
Nextjs と Firebase でどのように実現するか迷ったので参考になると嬉しいです

参考

GitHubで編集を提案

Discussion