問い合わせフォーム→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 │ チャンネル │
└──────────────┘ └──────────────┘ └──────────────┘
ブラウザ サーバー リアルタイム通知流れはシンプルです:
- ユーザーがフォームに入力して送信
- Next.jsのAPI Routeがリクエストを受け取る
- API RouteがDiscord WebhookにEmbed形式で転送
- Discordチャンネルに通知が届く(スマホにもプッシュ通知)
外部サービスへの登録不要。Next.js + Discord(無料)だけで完結します。
3. Step 1: Discord Webhookを作る
まず、通知を受け取るDiscordサーバーにWebhookを作成します。
チャンネル設定を開く
通知を受け取りたいチャンネルの⚙️をクリック
「連携サービス」→「ウェブフック」
左メニューから選択
「新しいウェブフック」をクリック
名前は「お問い合わせ通知」など、わかりやすいものに
「ウェブフック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連携まで、 あなたの業務に合わせた自動化をご提案します。
無料で相談する →