Slack 通知

新しい問い合わせを Slack に知らせる任意の通知です。応答の後に送り、個人情報を含みません。

Worker のシークレット SLACK_WEBHOOK_URL に Slack の Incoming Webhook の URL を設定すると、新しい問い合わせが来たときに Slack に通知が届きます。通知は訪問者への応答の後に送ります。サイト名とフォームを送信したページを示し、個人情報も問い合わせの内容も含みません。

通知の見た目

example.com の /contact から問い合わせが届き、言語を日本語にしている場合、通知は上から次のように並びます。

部分表示する内容
見出し🌙 新しいお問い合わせ
本文example.com に新しいお問い合わせが届きました。
受信日時2026-10-23 22:35 のような日時。読む人のタイムゾーンで表示します
ページ/contact
注記この通知には、個人情報やフォームの入力内容を含めていません。
ボタン管理画面で開く。押すとその問い合わせの詳細が開きます。いつもどおり Passkey でのログインが必要です

英語にした場合、見出しは「🌙 New inquiry」になります。Slack の通知バナーとスクリーンリーダーには、本文を太字なしにした「example.com に新しいお問い合わせが届きました。」という 1 行の文が使われます。

通知に含むもの

  • サイト名。 既定ではリクエストのホスト名で、たとえば example.com です。別の名前を出すときは siteName オプションを設定します。
  • 受信日時。 Slack の日付トークンで送るので、Slack が読む人それぞれのタイムゾーンで表示します。日付はどの言語でも 2026-10-23 の形です。時刻は読む人の Slack の設定に従うので、22:35 とも 10:35 PM とも表示されます。日付トークンを表示できない Slack アプリでは、2026-10-23 13:35 UTC のように UTC で表示されます。
  • フォームを送信したページ。 /contact のようなパスだけです。クエリ文字列や # 以降は取り除きます。ページのパスがわからないときは、この部分を出しません。
  • 管理画面の該当の問い合わせを開くボタン。

メールアドレス、名前、フォームの項目の値、プルダウンで選ばれた選択肢、IP アドレス、ユーザーエージェントは含めません。管理者がフォームに項目を足しても、これは変わりません。

言語とサイト名

どちらも、サイトの src/worker.ts にある createGoodnightWorker() のオプションです。

src/worker.ts
export default createGoodnightWorker({
  locale: siteConfig.defaultLocale,
  siteName: 'My Company',
});
  • locale は通知の言語で、ja か en を指定します。指定しなければ英語です。スターターはサイトの既定の言語を渡しています。
  • siteName は、通知に出すホスト名の代わりの名前です。空白だけの値は、設定していないものとして扱います。Slack には文字としてそのまま表示します。*、_、~、バッククォートは全角の文字に置き換えるので、太字などの書式にはなりません。< と > でリンクやメンションを作ることもできません。80 文字を超える名前は切り詰め、末尾を … にします。

そのほかの決まり

  • 送る先は Slack だけです。 Worker は、スパムの疑いがない問い合わせを保存した後、Webhook に投稿します。URL は https://hooks.slack.com/services/T…/B…/… の形式である必要があり、最後の部分の後ろには、スラッシュも含めて何も付けられません。ほかの値は無視し、管理画面の問い合わせ一覧に、URL の形式が正しくないことを知らせる警告を出します。警告に URL は表示しません。
  • 成功を記録し、再送はしません。 投稿に成功したら、Worker は notified_at を記録します。失敗しても送り直しません。
  • スパムの疑いは通知しません。 対象は、人には見えない入力欄である honeypot に値が入っている送信と、スパムスコアが 3 以上の送信です。これらも保存し、管理画面の スパムの疑い に表示します。問い合わせフォームの保護を参照してください。

コードでは

問い合わせ API は、行を保存したあと、スパムの疑いがない問い合わせのときだけ通知を始めます。

packages/server/src/features/contact/contact-routes.ts
// 8. After the response: notification (never for suspected spam) and
//    pruning.
const webhookUrl = env.SLACK_WEBHOOK_URL;
if (!suspectedSpam && isSlackWebhookUrl(webhookUrl)) {
  runInBackground(c, 'slack', () =>
    sendSlackNotification({
      db: env.DB,
      webhookUrl,
      submissionId: id,
      payload: buildSlackPayload({
        locale: options.locale,
        receivedAt: now,
        adminUrl: `${origin}/admin/inquiries/${id}`,
        siteName: options.siteName ?? new URL(c.req.url).hostname,
        pagePath: data.pagePath,
      }),
    }),
  );
}

メッセージは、次の値だけから組み立てます。訪問者が入力したものを受け取る引数はありません。

packages/server/src/features/notify/slack.ts
export const buildSlackPayload = ({
  locale,
  receivedAt,
  adminUrl,
  siteName,
  pagePath,
}: {
  locale: Locale;
  receivedAt: number;
  adminUrl: string;
  siteName: string;
  pagePath?: string | null;
}): SlackPayload => {

なぜこう作るか

  • 応答は Slack を待ちません。 投稿は、問い合わせを保存して訪問者に応答した後、ctx.waitUntil の中で動きます。Slack が止まっていても、問い合わせを失うことも、訪問者にエラーを見せることもありません。
  • 個人情報を Cloudflare の外に出しません。 Slack は別のサービスです。送るのはサイト名、ページのパス、日時、リンクだけなので、問い合わせの内容は D1 データベースにとどまります。
  • フォームの入力内容は送りません。 フォームで何を尋ねるかは管理者が決めるので、どの項目なら送ってよいかをサーバーは判断できません。
  • サイト名でサイトを見分けられます。 複数のサイトが同じチャンネルに通知しても、どのサイトに届いた問い合わせかがわかります。
  • Cloudflare Queues も再送も使いません。 管理画面のヘッダーには未読件数が常に出ています。通知を見逃しても、問い合わせを見逃すことにはなりません。
  • Webhook の URL はシークレットです。 URL を知っている人は誰でもチャンネルに投稿できます。そのため Worker のシークレットとして持ち、管理画面にも表示しません。

目次