📨

SendGridを利用してNext.js(App Router)にお問い合わせフォームを実装する

に公開

Next.jsにSendGridを利用したお問い合わせフォームを実装します。Next.jsはApp Routerを利用します。App Routerの概要はこちら↓
https://nextjs.org/docs/app
Next.jsのプロジェクトはnpx create-next-app@latestしてローカルでビルドできている状態から始めます。(App Router、TypeScript)

実装する機能

  • フォームからの入力内容を、指定したメールアドレスに送信(SendGrid)
  • ユーザーへの自動返信(SendGrid)

準備するもの

  • Next.js プロジェクト:npx create-next-app@latestコマンドで作成したプロジェクト。App Routerを利用します。本記事ではTypeScriptを前提に進めます。
  • SendGrid アカウント:無料のアカウントで大丈夫です。APIキーが必要です。
  • 自動返信に使うメールアドレス:Gmailなどでもテストは可能です。
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like your code inside a `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to use Turbopack for `next dev`? … No
✔ Would you like to customize the import alias (`@/*` by default)? … Yes
✔ What import alias would you like configured? … @/*

インストール時の設定はこのように答えました。

npx create-next-app@latest ひとまずビルドするとこんな感じ。

.env.local

APIキーなど取得したときにすぐコピペしたいので先に用意しておきます。
以下の環境変数を利用します。

.env.local
SENDGRID_API_KEY=SendGridで取得するAPIキー
SENDGRID_FROM_EMAIL=SendGridで認証した送信元メールアドレス
SENDGRID_TO_EMAIL=管理者(自分)が受信するメールアドレス
SENDGRID_AUTOREPLY_TEMPLATE_ID=自動返信用のテンプレートID

プロジェクトのルートディレクトリにこのファイルを設置します。

SendGridの設定

まずはSendGridの設定をしてしまいます。

  • APIキーの取得
  • 送信者情報の認証
  • 自動返信メールの作成(Dynamic Templateを利用)
twilioアカウント作成時の注意事項

twilioアカウント作成をGoogleアカウントの連携から行ってしまうと、Googleアカウントの名前が日本語だと詰んでしまうので注意が必要です。メールアドレスでの登録が無難です。

名前が日本語でプリセットされているのに文字数がカウントされず進めなくなる現象。(名前は適当です。)

APIキーの取得

SendGridの管理画面にログインしてAPIキーを取得します。

  1. SendGridにログインし、左側のメニューから [Settings] > [API Keys] を選択します。
  2. [Create API Key] をクリックします。
  3. API Key Name に名前を入力します。適当なもので良いです。
  4. API Key Permissions は [Full Access] を選択し、[Create & View] をクリックします。
  5. 表示されたAPIキーをコピーして .env.localSENDGRID_API_KEYに設定して保存。(ここでコピーしないとこのAPIキーは表示できなくなるので必ずここでコピペを行っておく)
.env.local
SENDGRID_API_KEY=SendGridで取得するAPIキー ←ここ
SENDGRID_FROM_EMAIL=SendGridで認証した送信元メールアドレス
SENDGRID_TO_EMAIL=管理者(自分)が受信するメールアドレス
SENDGRID_AUTOREPLY_TEMPLATE_ID=自動返信用のテンプレートID

送信者情報の認証

SendGridからメールを送信するために、SendGridの管理画面から認証を行う必要があります。
認証には2種類あります。

1. ドメイン認証 (Domain Authentication):独自ドメインのメールアドレスを持っている人はこちらを選択します。DNSレコード(CNAME)を設定する必要があり、信頼性が最も高くなります。画面の指示に従い、利用するDNSサービスの管理画面でレコードを追加してください。基本的に本番で運用する場合はこの認証が必要となります。

2. 単一送信者認証 (Single Sender Verification):ドメインがない場合や、テスト目的の場合はこちらを選択します。特定のメールアドレス(例: your-name@gmail.com)を認証します。認証したいメールアドレス宛に確認メールが届くので、リンクをクリックして認証を完了させます。

例えばGmailで送信テストだけを行う場合は2で大丈夫です。ただし送信テスト時に、メールの受信が弾かれてしまう場合もあります。
エラーはSendGridのActivityから確認することができます。

Your email has been rate limited because the From: header (RFC5322) in this message isn't aligned with either the authenticated SPF or DKIM organizational domain. To learn more about DMARC alignment, visit https://support.google.com/a?p=dmarc-alignment To learn more about Gmail requirements for bulk senders, visit https://support.google.com/a?p=sender-guidelines.

要は「あなたが送ろうとしたメールはFromヘッダーのドメインと認証されたドメイン(SPF/DKIM)が一致していないのでレート制限をかけました。」という感じです。何度も送らなければ届くのでテスト自体は可能です。

認証したメールアドレスを.env.localに書いておきます。
SENDGRID_TO_EMAILはお問い合わせが来たことを通知するメールアドレスなので、なんでも大丈夫です。こちらも書いて保存しておきます。

.env.local
SENDGRID_API_KEY=SendGridで取得するAPIキー
SENDGRID_FROM_EMAIL=SendGridで認証した送信元メールアドレス ←ここ
SENDGRID_TO_EMAIL=管理者(自分)が受信するメールアドレス ←ここ
SENDGRID_AUTOREPLY_TEMPLATE_ID=自動返信用のテンプレートID

自動返信メールの作成(Dynamic Templateを利用)

自動返信メールの設定を行います。
まずはSendGrid側でメールテンプレートを作成する必要があります。

  1. SendGridにログインし、左側のメニューから Email API > Dynamic Templates をクリックします。
  2. Create a Dynamic Template ボタンをクリックします。
  3. テンプレートの識別に使う名前を入力します(例:お問い合わせ自動返信contact-form-autoresponder)。入力後、Createボタンを押します。
  4. 作成したテンプレートのリストが表示されるので、今作ったテンプレートをクリックし、Add Version ボタンを押します。
  5. Select a template の画面になるので、Blank Templateを選択し、エディタはCode Editorを選びます。
  6. メールの内容を編集します。Subjectに件名を入れ、HTML Bodyに自動返信の内容を記載します。
HTML Body
<p>{{name}}様</p>
<p>この度はお問い合わせいただき、誠にありがとうございます。</p>
<p>以下の内容でお問い合わせを受け付けました。</p>
<p>--------------------</p>
<p>お名前: {{name}}様</p>
<p>メールアドレス: {{email}}</p>
<p>お問い合わせ内容:</p>
<p>{{message}}</p>
<p>--------------------</p>
<p>内容を確認の上、担当者より改めてご連絡いたしますので、今しばらくお待ちください。</p>
<br>
<p>--</p>
<p>株式会社〇〇</p>
<p>ウェブサイト: https://example.com</p>
<p>--</p>
  1. 入力が終わったら、右上のSave Templateボタンを押して保存します。
  2. Dynamic Templatesの一覧画面に戻り、作成したテンプレート名のすぐ下に表示されているTemplate ID(d-から始まる文字列)をコピーします。.env.localにペーストします。
.env.local
SENDGRID_API_KEY=SendGridで取得するAPIキー
SENDGRID_FROM_EMAIL=SendGridで認証した送信元メールアドレス
SENDGRID_TO_EMAIL=管理者(自分)が受信するメールアドレス
SENDGRID_AUTOREPLY_TEMPLATE_ID=自動返信用のテンプレートID ←ここ

APIルートの作成

SendGridライブラリのインストール

作成したプロジェクトのルートディレクトリに移動し、SendGridを扱うための公式ライブラリをインストールします。

npm install @sendgrid/mail

APIルートの作成

ユーザーがフォームを送信した際に、サーバー側でメールを送信する処理を作成します。

src/app/api/send-email/route.ts
import { NextResponse } from 'next/server';
import sgMail from '@sendgrid/mail';

// SendGrid APIキーを設定
if (!process.env.SENDGRID_API_KEY) {
  throw new Error("SENDGRID_API_KEY is not defined in .env.local");
}
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

export async function POST(req: Request) {
  const toEmail = process.env.SENDGRID_TO_EMAIL;
  const fromEmail = process.env.SENDGRID_FROM_EMAIL;
  const autoReplyTemplateId = process.env.SENDGRID_AUTOREPLY_TEMPLATE_ID;

  if (!toEmail || !fromEmail || !autoReplyTemplateId) {
    console.error("Email environment variables are not set.");
    return NextResponse.json({ error: 'Server configuration error.' }, { status: 500 });
  }

  try {
    // リクエストボディからフォームデータを取得
    const body = await req.json();
    const { name, email, message } = body;

    // バリデーション
    if (!name || !email || !message) {
      return NextResponse.json({ error: 'Missing required fields.' }, { status: 400 });
    }

    // A. 管理者への通知メール
    const adminMail = {
      to: toEmail, // あなたが通知を受け取るメールアドレス
      from: fromEmail, // SendGridで認証した送信元メールアドレス
      subject: `【お問い合わせ】${name}様より`,
      html: `
        <p><strong>お名前:</strong> ${name}</p>
        <p><strong>メールアドレス:</strong> ${email}</p>
        <p><strong>お問い合わせ内容:</strong></p>
        <p>${message.replace(/\n/g, '<br>')}</p>
      `,
    };

    // B. ユーザーへの自動返信メール
    const userMail = {
      to: email,
      from: fromEmail,
      templateId: autoReplyTemplateId,
      dynamicTemplateData: {
        name,
        email,
        message,
      },
    };

    // A、Bを同時に送信
    await sgMail.send([adminMail, userMail]);
    return NextResponse.json({ success: true, message: 'Message sent successfully.' });
  } catch (error) {
    console.error('Error in send-email API:', error);
    return NextResponse.json({ error: 'Error sending message.' }, { status: 500 });
  }
}

お問い合わせフォームの作成

ユーザーがお問い合わせするフロント側を作成します。
この例ではスタイリングはTailwind CSSを使用しています。

src/app/contact/page.tsx
'use client';

import { useState, type FormEvent } from 'react';

export default function ContactPage() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [message, setMessage] = useState('');

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setStatus('loading');
    setMessage('');

    const formData = new FormData(event.currentTarget);
    const data = {
      name: formData.get('name'),
      email: formData.get('email'),
      message: formData.get('message'),
    };

    try {
      const response = await fetch('/api/send-email', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }

      setStatus('success');
      setMessage('お問い合わせありがとうございます。メッセージは正常に送信されました。');
      (event.target as HTMLFormElement).reset();

    } catch (error) {
      console.error('Fetch error:', error);
      setStatus('error');
      setMessage('メッセージの送信に失敗しました。時間をおいて再度お試しください。');
    }
  };

  return (
    <div className="max-w-xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">お問い合わせフォーム</h1>

      <form onSubmit={handleSubmit} className="space-y-6">
        <div>
          <label htmlFor="name" className="block text-sm font-medium text-gray-700">お名前</label>
          <input type="text" id="name" name="name" required className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" />
        </div>

        <div>
          <label htmlFor="email" className="block text-sm font-medium text-gray-700">メールアドレス</label>
          <input type="email" id="email" name="email" required className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" />
        </div>

        <div>
          <label htmlFor="message" className="block text-sm font-medium text-gray-700">お問い合わせ内容</label>
          <textarea id="message" name="message" rows={4} required className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"></textarea>
        </div>

        <div>
          <button
            type="submit"
            disabled={status === 'loading'}
            className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
          >
            {status === 'loading' ? '送信中...' : '送信する'}
          </button>
        </div>
      </form>

      {message && (
        <div className={`mt-4 text-sm ${status === 'success' ? 'text-green-600' : 'text-red-600'}`}>
          {message}
        </div>
      )}
    </div>
  );
}

最終的なファイル構成

ファイル構成(省略しているファイル、ディレクトリもあります)
next-project/
├── public/
├──src/
│   ├── app/
│   │   └── api/
│   │   │   └── send-email/
│   │   │       └── route.ts
│   │   └── contact/
│   │   │       └── page.tsx
│   │   └── global.css
│   │
│   └── components/
│
├── .env.local
├── next.config.js
├── package.json
└── tsconfig.json

動作確認

npm run dev

ブラウザでhttp://localhost:3000/contactにアクセスします。
フォームに情報を入力し、「送信する」ボタンをクリックして送信してみてください。
問題なく動作すれば.env.localで指定したSENDGRID_TO_EMAILの受信トレイに、フォームの内容が記載されたメールが届くはずです。SendGridのActivityメニューからもメールの送信状況を確認することができます。

Discussion