問い合わせフォーム→Discord自動通知の作り方
Next.js×Webhook実践ガイド

実践ガイドハンズオン

「お問い合わせが来たのに気づかなかった…」そんな経験はありませんか?
この記事では、Webサイトの問い合わせフォームからDiscordに自動通知する仕組みを、 実際のコード付きで解説します。メール通知だと埋もれがちな問い合わせも、 Discordならチーム全員がリアルタイムで確認できます。

⏱ 読了時間:約10分 | 実装時間:約30分

1. なぜDiscord通知なのか?

問い合わせの通知手段として、メール・Slack・LINE・Discordなどがありますが、小規模チームや個人事業主にはDiscordが最適です。

通知手段費用設定の簡単さリアルタイム性
メール無料〜△ SMTP設定が必要△ 埋もれやすい
Slack無料〜¥1,050/人/月○ Webhookで簡単
LINE Notify無料(2025年3月終了)
Discord ✅完全無料◎ 最も簡単

💡 ポイント: DiscordのWebhookはURLを1つ取得するだけで使えます。 APIキーの申請もOAuth設定も不要。最もシンプルな通知手段です。

2. 仕組みの全体像

┌──────────────┐     POST     ┌──────────────┐    Webhook    ┌──────────────┐
│  お問い合わせ  │ ──────────> │  API Route   │ ──────────> │   Discord    │
│   フォーム     │   JSON      │ (Next.js)    │   Embed      │  チャンネル   │
└──────────────┘             └──────────────┘              └──────────────┘
     ブラウザ                     サーバー                    リアルタイム通知

流れはシンプルです:

  1. ユーザーがフォームに入力して送信
  2. Next.jsのAPI Routeがリクエストを受け取る
  3. API RouteがDiscord WebhookにEmbed形式で転送
  4. Discordチャンネルに通知が届く(スマホにもプッシュ通知)

外部サービスへの登録不要。Next.js + Discord(無料)だけで完結します。

3. Step 1: Discord Webhookを作る

まず、通知を受け取るDiscordサーバーにWebhookを作成します。

1

チャンネル設定を開く

通知を受け取りたいチャンネルの⚙️をクリック

2

「連携サービス」→「ウェブフック」

左メニューから選択

3

「新しいウェブフック」をクリック

名前は「お問い合わせ通知」など、わかりやすいものに

4

「ウェブフックURLをコピー」

このURLを後で使います。外部に漏れないよう注意!

# 取得したURLの形式

https://discord.com/api/webhooks/123456789/abcdefg...

📌 補足: Webhook URLには認証情報が含まれているため、 環境変数(.env.local)で管理するのがベストプラクティスです。

4. Step 2: お問い合わせフォームを作る

React(Next.js App Router)でフォームコンポーネントを作ります。 バリデーション付きで、送信中はローディング表示します。

// src/components/ContactForm.tsx

'use client';
import { useState } from 'react';

export default function ContactForm() {
  const [form, setForm] = useState({
    name: '', company: '', email: '', message: ''
  });
  const [status, setStatus] = useState<
    'idle' | 'sending' | 'sent' | 'error'
  >('idle');
  const [errors, setErrors] = useState<Record<string, string>>({});

  // バリデーション
  function validate() {
    const e: Record<string, string> = {};
    if (!form.name.trim()) e.name = 'お名前は必須です';
    if (!form.email.trim()) e.email = 'メールアドレスは必須です';
    else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email))
      e.email = '正しいメールアドレスを入力してください';
    if (!form.message.trim()) e.message = 'ご相談内容は必須です';
    return e;
  }

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const v = validate();
    if (Object.keys(v).length > 0) {
      setErrors(v);
      return;
    }
    setErrors({});
    setStatus('sending');

    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(form),
      });
      if (!res.ok) throw new Error();
      setStatus('sent');
    } catch {
      setStatus('error');
    }
  }

  if (status === 'sent') {
    return (
      <div className="text-center py-12">
        <p className="text-2xl mb-2">✅</p>
        <p className="font-bold">送信完了しました!</p>
        <p className="text-sm text-gray-500">
          通常1営業日以内にご連絡いたします。
        </p>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label className="block text-sm font-medium mb-1">
          お名前 <span className="text-red-500">*</span>
        </label>
        <input
          type="text"
          value={form.name}
          onChange={e => setForm({...form, name: e.target.value})}
          className="w-full border rounded-lg px-4 py-2"
        />
        {errors.name && (
          <p className="text-red-500 text-xs mt-1">{errors.name}</p>
        )}
      </div>

      <div>
        <label className="block text-sm font-medium mb-1">
          会社名・屋号(任意)
        </label>
        <input
          type="text"
          value={form.company}
          onChange={e => setForm({...form, company: e.target.value})}
          className="w-full border rounded-lg px-4 py-2"
        />
      </div>

      <div>
        <label className="block text-sm font-medium mb-1">
          メールアドレス <span className="text-red-500">*</span>
        </label>
        <input
          type="email"
          value={form.email}
          onChange={e => setForm({...form, email: e.target.value})}
          className="w-full border rounded-lg px-4 py-2"
        />
        {errors.email && (
          <p className="text-red-500 text-xs mt-1">{errors.email}</p>
        )}
      </div>

      <div>
        <label className="block text-sm font-medium mb-1">
          ご相談内容 <span className="text-red-500">*</span>
        </label>
        <textarea
          rows={5}
          value={form.message}
          onChange={e => setForm({...form, message: e.target.value})}
          className="w-full border rounded-lg px-4 py-2"
        />
        {errors.message && (
          <p className="text-red-500 text-xs mt-1">{errors.message}</p>
        )}
      </div>

      <button
        type="submit"
        disabled={status === 'sending'}
        className="w-full bg-primary text-white font-bold
                   py-3 rounded-lg hover:bg-primary/90
                   disabled:opacity-50"
      >
        {status === 'sending' ? '送信中...' : '無料で相談する →'}
      </button>
    </form>
  );
}

実装のポイント:

  • インラインバリデーションで送信前にエラーを表示
  • 送信中はボタンを無効化(二重送信防止)
  • 送信完了後はフォームを完了メッセージに差し替え

5. Step 3: API Routeでフォーム→Discordを繋ぐ

ここが核心部分です。Next.jsのAPI Route(Route Handler)で、 フォームの送信データをDiscord Webhookに転送します。

// src/app/api/contact/route.ts

import { NextRequest, NextResponse } from 'next/server';

// 環境変数からWebhook URLを取得
const WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL || '';

export async function POST(req: NextRequest) {
  try {
    const { name, company, email, message } = await req.json();

    // 必須チェック(サーバーサイド)
    if (!name || !email || !message) {
      return NextResponse.json(
        { error: '必須項目を入力してください' },
        { status: 400 }
      );
    }

    // Discord Embed形式でメッセージを組み立て
    const fields = [
      { name: 'お名前', value: name, inline: true },
      { name: 'メール', value: email, inline: true },
    ];
    if (company) {
      fields.push({
        name: '会社名・屋号', value: company, inline: true
      });
    }
    fields.push({
      name: 'ご相談内容', value: message, inline: false
    });

    const discordMessage = {
      embeds: [{
        title: '📩 新規お問い合わせ',
        color: 0x00d4ff,   // 水色
        fields,
        timestamp: new Date().toISOString(),
      }],
    };

    // Discord Webhookに送信
    const res = await fetch(WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(discordMessage),
    });

    if (!res.ok) {
      console.error('Webhook error:', await res.text());
      return NextResponse.json(
        { error: '送信に失敗しました' },
        { status: 500 }
      );
    }

    return NextResponse.json({ ok: true });
  } catch (e) {
    console.error('Contact API error:', e);
    return NextResponse.json(
      { error: 'サーバーエラー' },
      { status: 500 }
    );
  }
}

環境変数の設定:

# .env.local

DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/あなたのID/あなたのトークン

⚠️ セキュリティ注意: Webhook URLをフロントエンドのコードに直接書かないでください。 必ずAPI Route(サーバーサイド)経由で送信し、.env.localで管理します。

6. Step 4: バリデーションとUX改善

実用レベルにするために、以下のUX改善を追加します。

📝 インラインバリデーション

送信ボタンを押す前に、各フィールドのエラーを即座に表示。 メールアドレスは正規表現でフォーマットチェック。

// メールアドレスの形式チェック
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  errors.email = '正しいメールアドレスを入力してください';
}

⏳ ローディング状態

送信中はボタンを「送信中...」に変更+無効化。二重送信を防止。

<button disabled={status === 'sending'}>
  {status === 'sending' ? '送信中...' : '無料で相談する →'}
</button>

✅ 完了画面

送信成功後はフォームを非表示にし、完了メッセージを表示。 ユーザーに「ちゃんと届いた」という安心感を与えます。

🛡️ サーバーサイドバリデーション

フロントだけでなく、API Route側でも必須チェックを実施。 不正なリクエストを弾くための二重防御です。

7. カスタマイズ例

基本形ができたら、用途に合わせてカスタマイズできます。

🎨 Embedの色を変える

colorの値を変更するだけで、 通知の印象を変えられます。

// 色の例
color: 0x00d4ff,  // 水色(デフォルト)
color: 0xff6b00,  // オレンジ(緊急用)
color: 0x00c853,  // 緑(成功通知)
color: 0x7c4dff,  // 紫(VIP問い合わせ)

📋 フィールドを追加する

電話番号、予算、希望納期など、必要なフィールドを自由に追加。

// 予算フィールドを追加
fields.push({
  name: '想定予算',
  value: budget || '未回答',
  inline: true,
});

🔔 メンション付き通知

特定のロールやユーザーにメンションして、確実に気づかせる。

const discordMessage = {
  content: '<@&123456789> 新しい問い合わせです!',
  embeds: [{ /* ... */ }],
};

📊 スプレッドシート連携

Discord通知と同時に、Google スプレッドシートにも記録すれば、 問い合わせの管理・分析が簡単に。Google Sheets APIまたはGASと組み合わせて実現できます。

8. コスト:完全無料

¥0

Discord Webhook

¥0

Next.js API Route

¥0

Vercel(無料枠)

Vercelの無料枠(100GB帯域/月)で十分運用可能。 月間1,000件の問い合わせが来ても無料です。

9. 次のステップ:AI自動返信を追加する

今回作った仕組みにAI自動返信を追加すると、さらに強力になります。

発展形のイメージ:

1. ユーザーが問い合わせ送信
2. Discord に通知(← 今回作ったもの)
3. Claude API で問い合わせ内容を分析
4. 自動で一次返信メールを送信
5. 対応が必要な場合はDiscordで担当者にメンション

この発展形については、次回の記事で詳しく解説します。

まとめ

  • Discord WebhookはURL取得だけで使える最もシンプルな通知手段
  • Next.js API Routeでセキュアにフォーム→Discord連携
  • バリデーション・ローディング・完了画面でUXも万全
  • 運用コストは完全無料
  • AI自動返信への拡張も簡単

「設定がうまくいかない」「もっと高度なことをしたい」

Discord通知だけでなく、AI自動返信・顧客管理・CRM連携まで、 あなたの業務に合わせた自動化をご提案します。

無料で相談する →

📚 関連記事