LoginSignup
4
2

More than 1 year has passed since last update.

NextJSでgmailのお問合せフォームをnodemailerで無料で作る 2022

Last updated at Posted at 2022-04-20

nodemailerをNextJS上で実装する際に少し詰まったため記事にしました。

gmailをAPI叩いて使うために必要な"安全性の低いアプリ"の設定でしたが2022年5月から"安全性の低いアプリ"の設定ができなくなるため、他の設定が必要でしたが必要な設定がわからなくて少し詰まりました。

この記事を書いているのは4/20ですが自分のGoogleアカウント管理画面からはすでに設定する項目がなくなっていました。

ソース:https://support.google.com/accounts/answer/6010255?hl=ja
スクリーンショット 2022-04-20 12.43.23.png

結論からいうと "Googleアカウント管理画面HOME > セキュリティ > Googleへのログイン > アプリパスワード" で
設定して取得したシークレットキーを利用すれば使うことができました。

成果物

スクリーンショット 2022-04-20 13.08.22.png

最低限の項目だけを備えた簡易的なお問合せフォームです。

環境用意

NextJSの準備

npx create-next-app gmail-app --typescript

環境変数の準備

.env.local
NEXT_PUBLIC_MAIL_USER=hoge@gmail.com
NEXT_PUBLIC_MAIL_PASS=hogehoge

セキュリティページへ飛びます。
https://myaccount.google.com/security?hl=ja
スクリーンショット 2022-04-20 12.56.42.png

スクリーンショット 2022-04-20 12.57.28.png

その他(名前を入力)を選んで後は適当に設定したら"生成"をクリックするとシークレットキーが4文字区切りで出てくるのでそのままコピペして "NEXT_PUBLIC_MAIL_PASS=" に設定します

ライブラリの準備

npm i nodemailer
npm i -D @types/nodemailer

cssの追加

適当にスタイルを拾ってきて当てます
14 CSS Contact Forms よりZach Saucier氏のスタイルを利用させていただきました。
globalCSSに適当に追加しました。

styles/global.css
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

@import url(https://fonts.googleapis.com/css?family=Montserrat:400,700);

.feedback-input {
  color: white;
  font-family: Helvetica, Arial, sans-serif;
  font-weight: 500;
  font-size: 18px;
  border-radius: 5px;
  line-height: 22px;
  background-color: transparent;
  border: 2px solid #cc6666;
  transition: all 0.3s;
  padding: 13px;
  margin-bottom: 15px;
  width: 100%;
  box-sizing: border-box;
  outline: 0;
}

.feedback-input:focus {
  border: 2px solid #cc4949;
}

body {
  background: rgb(30, 30, 40);
}
form {
  max-width: 420px;
  margin: 50px auto;
}
textarea {
  height: 150px;
  line-height: 150%;
  resize: vertical;
}

[type='submit'] {
  font-family: 'Montserrat', Arial, Helvetica, sans-serif;
  width: 100%;
  background: #cc6666;
  border-radius: 5px;
  border: 0;
  cursor: pointer;
  color: white;
  font-size: 24px;
  padding-top: 10px;
  padding-bottom: 10px;
  transition: all 0.3s;
  margin-top: -4px;
  font-weight: 700;
}
[type='submit']:hover {
  background: #cc4949;
}

View側の準備

pages/index.tsx
import { useState } from 'react';
import type { NextPage } from 'next';

const Home: NextPage = () => {
  const [form, setForm] = useState({
    name: '',
    email: '',
    msg: '',
  });

  const handleSubmit = (e: React.FormEvent<HTMLInputElement>) => {
    e.preventDefault();

    fetch('/api/mail', {
      method: 'POST',
      body: JSON.stringify({
        name: form.name,
        email: form.email,
        msg: form.msg,
      }),
    })
      .then((res) => {
        console.log('Response received');
        if (res.status === 200) {
          console.log('Response succeeded!');
        } else {
          console.log(`Error: Status Code ${res.status}`);
        }
      })
      .catch((e) => {
        console.log(`Error: ${e}`);
      });
  };

  return (
    <form>
      <input
        onChange={(e) => {
          const val = e.currentTarget.value;
          setForm((props) => ({
            ...props,
            name: val !== null ? val : '',
          }));
        }}
        value={form.name}
        name="name"
        type="text"
        className="feedback-input"
        placeholder="Name"
      />
      <input
        onChange={(e) => {
          const val = e.currentTarget.value;
          setForm((props) => ({
            ...props,
            email: val !== null ? val : '',
          }));
        }}
        name="email"
        type="text"
        className="feedback-input"
        placeholder="Email"
      />
      <textarea
        onChange={(e) => {
          const val = e.currentTarget.value;
          setForm((props) => ({
            ...props,
            msg: val !== null ? val : '',
          }));
        }}
        name="text"
        className="feedback-input"
        placeholder="Comment"
      ></textarea>
      <input
        onClick={async (e) => {
          await handleSubmit(e);
        }}
        type="submit"
        value="SUBMIT"
      />
    </form>
  );
};

export default Home;

API側の準備

pages/api/mail.tsx
import { createTransport } from 'nodemailer';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const transporter = createTransport({
    port: 465,
    host: 'smtp.gmail.com',
    secure: true,
    auth: {
      user: process.env.NEXT_PUBLIC_MAIL_USER,
      pass: process.env.NEXT_PUBLIC_MAIL_PASS,
    },
  });

  const data = JSON.parse(req.body);
  await transporter.sendMail({
    from: process.env.NEXT_PUBLIC_MAIL_USER,
    to: data.email,
    subject: '以下の内容でお問い合わせを受け付けました',
    text: `
    名前
    ${data.name}
    
    メールアドレス
    ${data.email}
    
    お問い合わせ内容
    ${data.msg}
    `,
  });

  res.status(200).json({
    success: true,
  });
};

完成

あとは npm run dev で立ち上げて適当に項目を入れてSubmitすればメールの送信履歴ができているはず。

スクリーンショット 2022-04-20 13.11.42.png

Statusコードで500エラーが生じた場合は何かしらの設定を間違えている可能性があります。
Googleセキュリティページで2段階認証を設定してみるとか環境変数に誤りがないかなど色々と探ってみてください。

備考

今回はReact初心者の方も想定しているので使っていませんが自分が参考にした記事ではuseMailなどの名前でhookを作成している方もいました。
関数を切り分けた方が使い回しはしやすくなるかと。

4
2
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
4
2