前回までの記事で、コーポレートサイトはひとまず動く状態になりました。テンプレートの相性問題を乗り越え、画像も正しく表示されるようになった。あとは、実際に運用しながら育てていくだけ——そう思っていた矢先に、また新しい壁にぶつかりました。
お問い合わせフォームです。フォーム自体は、Payload公式のプラグインを使えば簡単に組み立てられました。項目を管理画面で作って、送信されたデータはコレクションに保存される。ここまでは迷いませんでした。
迷ったのは、その先でした。フォームが送信されたことに、どうやって気づくか。
定番は、メールでした。でも、それだけでいいのか
フォーム送信の通知といえば、真っ先に思い浮かぶのはメールです。多くのフォームツールが標準で持っている機能で、Payloadにも、送信内容を管理者宛にメールで知らせる仕組みは用意されていました。
素直に考えれば、これが一番手早い方法です。ただ、少し立ち止まって考えました。メールで実現できるのは、あくまで「メールを送る」ことだけです。今はお問い合わせフォームだけでも、この先LINEやDiscordなど、通知したい相手が増えていくのは目に見えていました。通知先が増えるたびに、その都度別の仕組みを一から用意することになる。
「気づきたい」というだけのことに対して、専用の仕組みを毎回ゼロから学び直すのは、割に合わない気がしました。
そこで選んだのが、n8nという自動化ツールでした。メールが「メールを送る」ことしかできない専用線だとすれば、n8nは「受け取ったものを、どこへでも配線できる」汎用線です。送る側をこの一本の配線に集約しておけば、この先通知先が増えても、同じ配線の上に積み上げていける。実装は、Payloadの保存フックからn8nのWebhookを叩き、n8nからSlackへ送るだけの、2ノードのシンプルな構成でした。
本番でつないでみて、Slackに通知が届いたときは、素直に「これでいい」と思いました。
機能を1つ足しただけのはずが、本番で「未入力」しか届かない
通知自体は届くようになったので、次にやろうとしたのは、ごく自然な改善でした。「誰から」「どんな内容で」問い合わせが来たのか、Slack上で一目でわかるようにする。送信者名と本文を、通知に載せるだけです。
n8nのSlackノードに、Payloadから渡ってきたフィールドを差し込む。実装自体は10分で終わりました。テスト送信し、Slackに通知が届いて、見た瞬間に「あれ」と思いました。
本番で送信してみると、Slackに届く通知には、名前も本文も「未入力」としか表示されなかったのです。
コードは書いた通りに動いているはずでした。n8nの設定も、間違っているようには見えません。でも、届くのは空欄だけ。ここから、3日間の調査が始まりました。
1つ目の真犯人:振り分けられていなかった入り口
最初に疑ったのは、n8n側のルーティングでした。実際、これは半分正解でした。当時のワークフローは、フォームの種類を問わず単一のwebhookに送信をまとめて流す作りで、フォームごとの振り分けができていませんでした。フォームIDごとにwebhookを分ける形に直し、これで解決したと思いました。
ところが、しばらくして確認すると、また「未入力」が再発していました。
2つ目の真犯人:目に見えない、たった2つの半角スペース
振り返ると、これは同じ不具合の再発ではなく、似た症状を持つ、まったく別の不具合でした。1つ目は「フォームの送信がn8nに届いていなかった」という、そもそも通知自体が届かないレベルの不具合。2つ目に見つかったのは、「n8nには届いているのに、値だけが空になる」という、まったく別の種類の不具合でした。
原因は、Payloadの管理画面でフォームの項目名を設定する際、前後に余分な空白が入っていたことでした。目視ではまず気づけません。この空白のせいで、n8n側でフィールド名をキーとして参照しても一致せず、Slackでは空欄として表示されていたのです。フィールド名をtrimする1行を足し、お問い合わせフォームの通知は、ようやく正しく表示されるようになりました。
3つ目の真犯人:形の違う2つのものを、無理に1つで語ろうとした
お問い合わせフォームの方は、実はここまでそれほど時間はかかりませんでした。厄介だったのは、そのあとです。
同じSlack通知の仕組みに、LINE公式アカウントからのメッセージも一緒に流そうとしました。フォームとLINE、2つの入り口を1つの通知ノードにまとめて、内容に応じて文面を出し分ける。そう考えて、1つのテキストの中に条件分岐を書き込みました。
ここではまりました。気づけば、さっきまで直っていたはずのお問い合わせ側の表示が、また崩れていたのです。
あとから見返してわかったのは、フォームとLINEでは、送られてくるデータの形がまったく違うということでした。LINE側は配列の中にメッセージ本体が入っている構造で、フォーム側は送信内容がまとまったオブジェクトです。この、形の異なる2つのデータを、1つの条件分岐で無理やりさばこうとしていました。LINE側を直せばフォーム側が壊れ、それを直すとLINE側がおかしくなる。そんなことを繰り返していました。
2時間の作業を3回、2日にわたって続けて、ようやく解決しました。原因がはっきりしないまま手を動かし続ける時間は、同じ場所をぐるぐる回っているような感覚でした。
なぜここまで時間がかかったのか
いくつか理由があったと思います。
1つは、コードの設計に「静かに失敗する」仕組みが入っていたことです。必要な設定が取得できない場合、エラーを出さずにそのまま処理を終えるようになっていました。一見安全に見えますが、裏を返せば「何も起きていないように見えて、実は動いていない」状態を生みます。実行履歴にすら痕跡が残らないため、原因を探す手がかりそのものが失われていました。
もう1つは、開発環境で動くことと、本番で安定して動き続けることの違いです。ローカルでの検証は、1つの入り口だけを想定していました。ところが本番では、フォームとLINE、2つの異なる入り口から、性質の違うデータが同時に流れてきます。性質の異なる2つの処理を1つのノードに詰め込んだことで、片方を直すたびにもう片方に影響が及ぶ状態になっていたのだと思います。
そしてこれは、技術的な話だけではありません。形の違う2つのものを、1つの言葉、1つの処理で語ろうとしたこと自体に、そもそも無理があったのだと思います。
次にSaaSを追加する人へ
この経験から、次にn8nへ新しいSaaS連携を追加するときのために、心に留めていることがあります。
エラーが出ないまま処理が終わる「静かな失敗」の設計を疑う習慣を持つこと。設定を変更したら、コード側だけでなく本番側の設定も必ず確認すること。目視ではなく、機械的な方法で確認する検証習慣を持つこと。そして何より、性質の異なる入り口が増えたら、1つの処理に無理やり詰め込まず、入り口ごとに処理を分けること。
最後の点は、今のワークフローにもまだ残っている課題です。次にDiscordやStripeなど、3つ目、4つ目の入り口を足すタイミングが来たら、その前に、入り口ごとに処理を独立させる整理をしようと思っています。動いているものを壊さずに直すのは、新しく作るより神経を使う作業ですが、次に同じ迷宮にはまらないための、必要な準備だと考えています。
おわりに
SaaSを1つ足すだけなら、実装は10分で終わります。けれど、それを本番で安定して運用するところまでを含めると、話はまったく別です。専用線を1本ずつ学び直すのではなく、汎用の配線を先につくっておく——その判断は間違っていなかったと、今でも思っています。ただ、配線を1本にまとめるという判断そのものが、形の違うものを無理に1つで扱おうとする落とし穴と、実は隣り合わせだったのだと、今回のことで気づかされました。
SaaSを1つ増やすたびに、時間を溶かすリスクも増える。けれど、その分だけ、次に同じ壁にぶつからないための引き出しも増えていく。そう思って、今日も1つずつ向き合っています。