問い合わせフォームの大半は営業メールだった — サーバサイドで機械判定する Phase1 を作った

目次

気づいたこと

サイトの問い合わせフォームを見ていて、違和感を持った。届くものの多くが、見込み客からの相談ではない。いわゆるフォーム営業である。会社名を名乗り、自社サービスを紹介し、提案したい、協業したい、という文面が並ぶ。

問題は混在していることにある。本来のコンバージョン(CV)とフォーム営業が同じ箱に入ってしまう。すると、成果の数字が読めない。問い合わせ件数は増えているのに、商談にならない。どの施策が効いたのかを判断できない。

最初にやるべきは、怪しいものを手で弾くことではない。手で弾く運用は、量が増えれば破綻するし、判断の基準も担当者の勘に依存してしまう。まず必要なのは、判定の精度を実データで確かめる土台である。そこで、送信のたびにサーバサイドで営業らしさを採点し、記録だけを残す Phase1 を実装した。GA4 への送信は、まだ行っていない。

フォーム営業が増える理由

営業メールが届く側の立場で考えると、対策は「送らないこと」ではない。送る側にとっては、フォームは費用がほぼゼロで、宛先を自動で集められる経路である。だから放っておけば増えていく。

対策の方向は2つあると考えている。1つは送信の段階で止めること。もう1つは、届いたものを分類して、成果の測定から切り離すことだ。前者は防御の強化であり、後者は測定の精度の問題である。

今回は後者を選んだ。理由は、防御だけでは数字が濁ったまま残るからだ。スパムを弾いても、弾き切れなかったものが CV として計上されれば、施策の評価は歪む。分類して測定から外す仕組みを先に作る。防御の強化は、判定の精度が見えてからでよいのではないか。

フックの位置を実測で特定する

フォームは mw-wp-form(5.0.3)で作られている。対象は /inquiry/ と /lp/ の2面である。両方ともデータベース保存は無効で、送信後の処理はメール送信とリダイレクトだけだった。

送信成功の直後に処理を差し込むには、どのタイミングでどんな値が渡るかを知る必要がある。ソースを読み、送信成功時に do_action('mwform_after_send_' . $form_key, clone $this->Data) が発火することを確認した。フォームキーは mw-wp-form-50 と mw-wp-form-1019 である。取得できるフィールドは、会社名・担当者名・電話・メール・内容・要望などである。

実装は mu-plugin として置くことにした。テーマに依存せず、テーマの切り替えやアップデートで消えない。プラグイン一覧から無効化されることもない。読み込む順番も自分で決められる。フォームの送信に常時立ち会う処理は、こうした配置の安定性が効いてくる。

判定はスコア方式にする

営業かどうかを1つの条件で切ろうとすると、必ず漏れる。たとえば「ご提案」という語が本文にあれば営業、と決めると、本物の相談でもその語を使う人が弾かれてしまう。逆に言い回しを変えられれば逃げられる。単一条件の判定は、どうしてもどちらかに大きく倒れる。

そこで、複数の観点に点を配り、合計で判定する方式にした。

  • 強い営業キーワード(ご提案、弊社サービス、コスト削減、売上向上、集客、SEO対策、サイト制作、システム開発、アポイント、商談、業務提携、人材紹介、相互リンク 等): 各 +3
  • 弱い営業キーワード(弊社、ご連絡、ご案内、資料送付、ご検討、お見積、広告、デモ、効率化 等): 各 +1
  • フリーメール(無料で取得できるドメイン): +2
  • 電話番号が空: +1
  • 会社名が空: +1

合計6点以上を「営業の可能性が高い」、3〜5点を「疑わしい」、2点以下を「通常」とした。閾値と、加点の根拠になった語を必ず記録に残す。後から実データを見て閾値を動かせるようにするためである。

点数を付けるのは、判定を「正しい/間違い」の2値で固定しないためだ。境界をあとから動かせる形で記録しておけば、実データを見ながら調整できる。判定を最終決定ではなく、観測の道具として置く。これが Phase1 の設計意図である。

実装したスコア判定の骨格

加点の一覧はコードに直接書いた。設定画面を作るより、まず辞書を1箇所にまとめておく方が調整が速い。

// 強い営業キーワード(各 +3)
'ご提案', '弊社サービス', 'コスト削減', '売上向上', '集客', 'SEO対策',
'ホームページ制作', 'システム開発', 'アポイント', '商談', 'パートナー',
'業務提携', '営業代行', '人材紹介', 'リード獲得', '相互リンク', '記事掲載'

// 弱い営業キーワード(各 +1)
'弊社', 'ご連絡', 'ご案内', '資料送付', 'ご検討', 'お見積', '無料相談',
'広告', 'SNS運用', '導入事例', 'デモ', '効率化', 'DX支援'

スコアは合計で判定する。ただし、点数そのものより、加点の根拠になった語を配列で残す方が重要だ。どの語が効いて何点になったかが分かれば、閾値の調整は記録の集計だけで済む。「なぜこの送信が営業と判定されたか」を後から説明できることは、判定を運用に載せる前提条件になる。

対象フォームとフィールド

判定の対象は2面。mw-wp-form-50(/inquiry/)と mw-wp-form-1019(/lp/)である。フォームキーは投稿IDから作られるため、この番号がそのまま識別子になる。フォームを増やしたときは、フックを張るキーの一覧に足せばよい。

受け取るフィールドは、会社名・担当者名・電話・メール・内容・要望の6つ。このうち会社名・担当者名・内容・要望を連結した文字列に対してキーワードを探し、電話とメールは空かどうか・ドメインが何か、という形で別に扱う。文面の特徴と、文面以外の特徴を分けて数える。

両フォームともデータベース保存は無効である。つまり、送信の記録はメールとこのログだけに残る。だからこそ、判定結果を後から集計できる形で残す価値があると考えている。

フリーメールの扱い

無料で取得できるドメインを列挙し、そのドメインからの送信に2点を足す。判定に使うのは、たとえば次のような面々である。

gmail.com / yahoo.co.jp / hotmail.com / outlook.com / icloud.com
docomo.ne.jp / ezweb.ne.jp / softbank.ne.jp / nifty.com / biglobe.ne.jp

列挙にした理由は、判定の根拠を目で追えるようにするためである。パターンで「無料っぽいドメイン」を推測するより、一覧を持っておく方が、誤判定の原因を特定しやすい。ドメインは増減するので、リストは育てる前提で置いた。

なぜ無料メールと電話番号を見るのか

文面だけでは足りないので、文面以外の特徴も点にした。

企業からの相談であれば、会社のドメインのメールアドレスを使うことが多い。無料で取得できるドメインのメールは、個人の検討や、いわゆる一括送信の両方に現れる。単独では営業の証拠にならないが、加点材料としては弱くない。

電話番号が空、会社名が空、というのも同じ扱いである。商談を前提とした相談なら、連絡先が埋まっていることが多い。逆に、送信先を大量に回る側は、入力を省く。これらは単独では決め手にならないが、文面のキーワードと重なると判定が安定する。

弱い条件を足すことの意味は、強い条件の誤りを補うことにある。強いキーワードだけで切ると、言い回しを変えられたときに落ちる。弱い条件を重ねておくと、複数の小さな兆候が積み上がって判定に届く。

スコアの実例で確かめる

実際に届いた文面で試すと、判定はおおむね素直に動いた。「ご提案させていただけないでしょうか」に始まり「弊社サービス」と「コスト削減」が並ぶ文面は、強い語が3つ重なって9点。フリーメールと電話なしが加われば12点になる。一方、具体的な相談は自社の課題を語るが、提案の語は使わない。点数は2点以下に収まる。

境界に落ちるのは、「協業のご相談」のような文面である。語だけを見れば営業の語が並ぶが、本物の協業の申し出であることもある。だから「疑わしい」の区分を残した。2値で切らずに3値で置くことで、境界の事例を後から拾える。誤判定をゼロにするのではなく、誤判定の場所を観測できるようにする。ここが今回いちばん効いていると考えている。

ログはWeb公開領域の外に置く

判定結果には個人情報が含まれる。氏名、メールアドレス、電話番号、問い合わせ本文そのものが入る。これを wp-content 配下に置けば、設定次第で外部から取得できる可能性が残る。

置き場所は /var/lib/ 配下の専用ディレクトリにした。Web サーバの公開領域の外であり、Apache の設定に依存せず確実に非公開にできる。ディレクトリは存在しなければ作成し、権限は 0770 とした。

書き出し形式は JSON Lines にした。1送信=1行である。行単位なら、あとから集計しやすい。1行ずつ独立しているので、途中で壊れた行があっても他の行の解釈を阻害しない。ファイルは月次で分け、5MB を超えたら .1 へローテーションする。月次で分けるのは、調査のときに期間で切れるようにするためである。

送信フローを壊さない

フォームの送信処理に自分のコードを差し込むということは、自分の不具合が問い合わせの取りこぼしに直結するという意味である。このリスクを最優先で設計した。

フックの中の処理は全体を try/catch で囲む。例外が出ても、送信とリダイレクトは元のまま続く。記録に失敗してでも、問い合わせは届かせる。逆にすれば、記録の不具合が顧客の声を消すことになる。ログの欠落は後から気づけるが、届かなかった問い合わせは気づけない。

文字列検索には mb_strpos を使うが、mbstring が入っていない環境では strpos に切り替える。関数が無い環境で無言で失敗させないためである。判定が動かないことに気づかないまま運用するのが、いちばん危ない。動いていないことは、動いていることより見つけにくい。

配備とログの置き場所

実装は mu-plugin として配備した。プラグインのディレクトリに置くのではなく、必ず読み込まれる位置に置く。管理画面から誤って無効化できず、更新の影響も受けない。

ログのディレクトリは、無ければ作る。権限は 0770。書き込みは追記と排他ロックを付けて行う。同時に複数の送信が来ても、行が混ざらないようにする。1行が壊れても他が読める形式にしておくと、障害の切り分けが速い。

Phase1 は記録だけにする

今回の実装は、判定してログに書くだけである。GA4 へは送らない。

理由は順序である。判定の精度を確かめる前に、その判定で CV を分離してしまうと、数字が汚れる。営業メールを正規 CV として送れば成果を過大に見積もるし、逆に本物の相談を弾けば成果を過小に見積もる。まず判定を観測し、精度を確認し、そのうえで正規 CV だけを測定に送る。Phase2 で GA4 の Measurement Protocol に接続する予定である。

段階を分ける利点は、失敗したときの戻しやすさだ。記録だけなら、閾値が的外れでも実害は記録の中に閉じる。実装を戻すのは、ログを止めるだけである。一方、GA4 へ送ってしまえば、汚れた数字は後から消せない。測定の側は、後戻りがきかない。戻せない変更は、戻せる変更の後に置きたい。

運用で見るポイント

実装したあとに見るべきは、判定の内訳である。3区分がどの割合で出ているか、点数がどこに固まっているか、根拠になった語は何か。この3点を眺めると、閾値の妥当性が見えてくる。

「疑わしい」が異様に多いなら、閾値が低すぎるか、弱いキーワードが広すぎる。逆に「営業の可能性が高い」が少なすぎるなら、強いキーワードの辞書が足りない。数字を見ながら辞書と閾値を動かす。この調整を前提にした作りにしたことが、Phase1 のいちばんの成果である。

調整の順序にも方針を置いた。まず誤って弾いた事例を救う。CV の取りこぼしは、営業メールを1件通すより損害が大きい。辞書を増やすのは、そのあとでよい。

分かったこと

  • 判定の精度を上げる前に、判定を見られる状態を作る。記録が先、自動化が後。
  • 閾値は後から動かせるところに置く。根拠になった語も一緒に残す。
  • 個人情報を含むログは、公開領域の外へ。設定に依存しない置き場所を選ぶ。
  • フックに差し込むコードは、例外を飲み込んででも本来の処理を守る。
  • 閾値6と3という数字は、まだ経験則である。だから検証を前提にした作りにした。
  • 戻せない変更(測定への送信)は、戻せる変更(記録)の後に置く。
  • 判定は2値で切らず3値で置く。境界の場所が見えると、調整の当て所が分かる。

残った課題

精度の検証がまだである。実データが溜まったら、判定結果と実際の商談化を突き合わせて、閾値とキーワードを見直す。誤判定の事例を集め、強いキーワードの追加よりも、誤って弾いた事例の救済を優先する。CV を取りこぼす方が損害が大きいと考えている。

Phase2 では、正規 CV のみを GA4 へ送る際の識別子の扱いを詰める必要がある。フォームに GA4 のクライアントIDを持たせる方法、同意の扱い、計測の重複防止など、決めるべき点は多い。ここは実装前に設計を固めたい。記録が戻せる変更であるのに対し、測定は戻せない。段取りを逆にしないことが、この作業でいちばん効く原則だった。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

金重総合研究所の主席研究員。
子供の頃から研究者を目指し、ライフワークとして日々様々な研究をしています。
経営・マネジメント・金融・DXあたりが本職です。
私を採用したい人、私と一緒に働きたい人、一緒に知識を肥やしていきたい人はぜひお声がけ下さい。

目次