Loading...

2023-11-09(木) 15:00

👋 Next.jsでResendを使ったお問い合わせフォームを実装する

Next.jsResend
Next.jsでResendを使ったお問い合わせフォームの実装方法を解説します。

目次

前提と注意事項

以下が前提と注意事項になります。

  • Resend のアカウントと API キーは作成済みとします。
  • Next.js を使用します。

上記の Resend のアカウントと API キーの作成手順は以下にまとめていますので必要な方は参考にしてみてください。

✉️ メール配信サービスのResendを試す

開発者のためのメール配信サービスとして展開しているResendを使ってメール送信するまでの手順を解説します。

ritaiz.com

今回使った Next.js とその他のパッケージのバージョンは以下です。

package.json
"dependencies": {
    "@types/node": "20.5.6",
    "@types/react": "18.2.21",
    "@types/react-dom": "18.2.7",
    "autoprefixer": "10.4.15",
    "eslint": "8.48.0",
    "eslint-config-next": "13.4.19",
    "next": "14.0.0",
    "postcss": "8.4.28",
    "react": "^18",
    "react-dom": "^18",
    "react-hook-form": "^7.47.0",
    "resend": "^2.0.0",
    "tailwindcss": "3.3.3",
    "typescript": "5.2.2"
  }

この記事のゴール

今回の内容の重要な部分ではないですが、以下のようなお問い合わせフォームの内容を、Resend を使ってメールで送信することをゴールとします。

Resend のパッケージをインストールする

npmかyarnを使って Resend のパッケージをインストールします。

my-project
$ npm install resend
# or
$ yarn add resend

上記公式パッケージのリポジトリは以下です。

Resend Node.js SDK

Node.js library for the Resend API.

github.com

Resend の API キーを環境変数に追加する

環境変数に Resend の API キーを追加します。.envファイルに以下のように追加します。

.env
RESEND_API_KEY='re_************************'

また、メールの送信元として指定するメールアドレスも以下のように追加しておきます。

.env
RESEND_FROM_EMAIL='Ritaizヘルプデスク <[email protected]>'

メールアドレスのみを送信元として指定しても OK ですが、上記のように任意の文字列を送信者として含めることもできます。対応しているメールアプリであれば、メールアドレスの代わり、もしくは一緒に送信者名が表示されます。

これで Next.js で Resend を使用する準備が完了しました。

お問い合わせフォームを作成する

Next.js で適当なお問い合わせフォームを作成します。 ひとまず簡易のバリデーションで全てを詰め込んでいるコンポーネントです。 適宜エラーハンドリングをすることをおすすめします。 以下で重要なのは29-39行目で、ここでは後述する/api/sendに対して POST リクエストを送っています。 /api/sendに対してフォームで入力された値であるお名前(name)、メールアドレス(email)、メッセージ内容(message)を渡しています。
/api/sendで実際に Resend へのメール送信を行う処理を記述します。

app/components/ResendContactForm.tsx
'use client';
import { useState } from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
 
type FormInputs = {
  name: string;
  email: string;
  message: string;
};
 
export default function ResendContactForm() {
  const [loading, setLoading] = useState(false);
  const [isSubmitted, setIsSubmitted] = useState(false);
  const [isError, setIsError] = useState(false);
 
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<FormInputs>();
 
  const onSubmit: SubmitHandler<FormInputs> = async (data) => {
    console.log(data);
    setIsSubmitted(false);
    setIsError(false);
    setLoading(true);
 
    // /api/sendにPOSTリクエストを送る
    // /api/sendは後述
    const response = await fetch('/api/send', {
      method: 'POST',
      mode: 'same-origin',
      credentials: 'same-origin',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
 
    setLoading(false);
 
    if (response.status !== 200) {
      setIsError(true);
      return;
    } else {
      setIsSubmitted(true);
      reset();
    }
  };
 
  return (
    <>
      <form
        onSubmit={handleSubmit(onSubmit)}
        className='my-6 w-full md:w-4/5 mx-auto'
      >
        <div className='flex flex-col w-full'>
          {isSubmitted ? (
            <div
              className='bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4'
              role='alert'
            >
              <p className='font-bold'>送信しました。</p>
            </div>
          ) : null}
          {isError ? (
            <div
              className='bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4 mb-4'
              role='alert'
            >
              <p className='font-bold'>送信に失敗しました。</p>
            </div>
          ) : null}
          <div className='flex flex-col sm:flex-row sm:items-center'>
            <div className='text-left text-gray-700 dark:text-white mb-2'>
              お名前
            </div>
          </div>
          <div className='flex flex-col sm:flex-row sm:items-center mb-5'>
            <div className='w-full'>
              <input
                {...register('name', { required: true })}
                className='py-2 px-3 w-full leading-tight rounded border appearance-none text-gray-700 focus:outline-none focus:shadow-outline'
              />
              {errors.name && <span>お名前は必須です。</span>}
            </div>
          </div>
          <div className='flex flex-col sm:flex-row sm:items-center'>
            <div className='text-left text-gray-700 dark:text-white mb-2'>
              メールアドレス
            </div>
          </div>
          <div className='flex flex-col sm:flex-row mb-5'>
            <div className='w-full'>
              <input
                {...register('email', {
                  required: true,
                  pattern: /^\S+@\S+$/i,
                })}
                className='py-2 px-3 w-full leading-tight rounded border appearance-none text-gray-700 focus:outline-none focus:shadow-outline'
              />
              {errors.email && (
                <span>有効なメールアドレスを入力してください。</span>
              )}
            </div>
          </div>
          <div className='flex flex-col sm:flex-row sm:items-center'>
            <div className='text-left text-gray-700 dark:text-white mb-2'>
              本文
            </div>
          </div>
          <div className='flex flex-col sm:flex-row mb-5'>
            <div className='w-full'>
              <textarea
                {...register('message', { required: true })}
                className='py-2 px-3 w-full leading-tight rounded border appearance-none text-gray-700 focus:outline-none focus:shadow-outline'
              />
              {errors.message && <span>メッセージは必須です。</span>}
            </div>
          </div>
        </div>
        <div className='flex justify-center items-center mb-10 md:mb-20 sm:ml-8'>
          <button
            type='submit'
            disabled={loading}
            className='flex justify-center items-center py-2 px-4 w-40 font-bold text-white bg-blue-800 hover:bg-blue-900 rounded-sm disabled:opacity-50 cursor-pointer'
          >
            {loading ? (
              <div role='status' className='mr-2'>
                <svg
                  aria-hidden='true'
                  className='w-8 h-8 mr-2 text-white animate-spin fill-blue-600'
                  viewBox='0 0 100 101'
                  fill='none'
                  xmlns='http://www.w3.org/2000/svg'
                >
                  <path
                    d='M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z'
                    fill='currentColor'
                  />
                  <path
                    d='M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z'
                    fill='currentFill'
                  />
                </svg>
                <span className='sr-only'>Loading...</span>
              </div>
            ) : null}{' '}
            {loading ? '送信中...' : '送信'}
          </button>
        </div>
      </form>
    </>
  );
}

送信するメールのテンプレートを作成する

Resend では、送信するメールのテンプレートを React のコンポーネントとして作成することができます。今回は簡単ですが以下のようなテンプレートを作成します。

components/Email/InquiryTemplate.tsx
import * as React from 'react';
 
interface InquiryEmailTemplateProps {
  senderName: string;
  content: string;
}
 
export const InquiryEmailTemplate: React.FC<
  Readonly<InquiryEmailTemplateProps>
> = ({ senderName, content }) => (
  <div>
    <h1>{senderName} 様</h1>
    <h2>お問い合わせありがとうございます。</h2>
    <p>以下の内容でお問い合わせを受け付けました。</p>
    <p>{content}</p>
  </div>
);
 
export default InquiryEmailTemplate;

上記はテキストだけのメールとなり、決して良い見た目ではありません。もしもっと見た目を良くしたい場合は、react-emailを使用するとメールのスタイリングに Tailwind やその他のコンポーネントを使用でき、簡単に良い見た目にすることができます。 以下にreact-emailを使ったテンプレートとそれを使って Resend で送信する実装をまとめたので必要な方は見てみてください。

🌈 react-emailを使ってTailwindスタイルのメールをResendで送信する

react-emailを使ってTailwindスタイルのメールをResendで送信する方法を解説します。

ritaiz.com

components/Email/InquiryTemplate.tsx
## Resend へのメール送信処理のための API ルートを実装する
 
`api/send/route.ts`を作成して、以下の内容にします。
 
```ts title="api/send/route.ts"
import { InquiryEmailTemplate } from '@/app/components/Email/InquiryTemplate';
import { NextResponse } from 'next/server';
import { Resend } from 'resend';
import * as React from 'react';
 
// 環境変数からResendのAPIキーを取得
const resend = new Resend(process.env.RESEND_API_KEY);
 
// 環境変数から送信元に指定するメールアドレスを取得
const fromEmail = process.env.RESEND_FROM_EMAIL;
 
export async function POST(request: Request) {
  // お問い合わせフォームからのデータを取得
  // name, email, message
  const req = await request.json();
 
  try {
    const { data, error } = await resend.emails.send({
      from: fromEmail ?? 'Acme <[email protected]>',
      to: [req.email],
      subject: 'お問い合わせありがとうございます',
      // InquiryTemplate.tsxで作成したテンプレートを使用
      react: InquiryEmailTemplate({
        senderName: req.name,
        content: req.message,
      }) as React.ReactElement,
    });
 
    if (error) {
      return NextResponse.json({ error });
    }
 
    return NextResponse.json({ data });
  } catch (error) {
    return NextResponse.json({ error });
  }
}

お問い合わせフォームの動作確認

あとは以下のように適当なページで上記で作成したResendContactFormを使用してフォームを送信してみると、本記事冒頭に載せたゴールと同じ結果を得られると思います。

app/send/page.tsx
// import先は適宜置き換えてください。
import ResendContactForm from '../components/Forms/ResendContactForm';
 
export default function Home() {
  return (
    <main>
      <div>
        <ResendContactForm />
      </div>
    </main>
  );
}

まとめ

Next.js で Resend を使ったお問い合わせフォームを実装する方法を解説しました。
Resend の公式パッケージを使うことで簡単にメール送信処理を記述できます。この記事では紹介していませんが、添付ファイルの送信ももちろん可能です。 その他の機能については以下の公式ドキュメントを参照してください。

Send Email

Start sending emails through the Resend Email API.

resend.com