ワークフロー
メッセージ生成
Sales Claw が「この会社だけに書いた」文面を生成する仕組みを、実装ファイルレベルで解説します。
1. アーキテクチャ概要
Sales Claw のメッセージ生成は「機械処理で事実を抽出」「AI で文面を起こす」を 明確に分離した 2 段構成です。事実は parallel-analysis.cjs が HTTP フェッチで集め、 文面は CLI エージェント (Claude / Codex / Gemini) がプロンプトに基づいて生成します。
- Phase A (並列・MCP 不使用) — 各社の公式サイトを HTTP で fetch し、事業領域・注力分野・ ギャップ・サイト抜粋を抽出。AI 推論はここでは行わず純粋なパース処理。
- Phase A.5 (CLI 文面生成) — Phase A の analysis から
buildMessagePrompt(analysis)が CLI 用プロンプトを構築。CLI がそれを受けて 1 社ずつ本文を最終化。 - Phase B (順次・MCP Playwright) — 生成した本文を実ブラウザのフォームに入力し、 スクリーンショットを撮ってユーザーの承認待ちに登録。
AI を呼ぶのは Phase A.5 のみで、しかも「プロンプトが完全に固まった状態」で呼ぶため、出力の 再現性とコストコントロールが効きます。フェールセーフ用に buildCustomMessage(analysis)のテンプレート生成も常に裏で動いており、CLI が失敗した場合のフォールバックとして機能します。
2. 2 フェーズ生成パイプライン
┌────────────── Phase A (並列, haiku 推奨) ──────────────┐
│ parallel-analysis.cjs │
│ ├─ fetchSite(companyUrl) … HTML + テキスト抽出 │
│ ├─ analyzeBusiness(text) … 事業領域・キーワード│
│ ├─ findKeyPages(html) … ニュース・採用 etc. │
│ └─ extractFormCandidates(html) … 問い合わせ導線 │
│ → analysis = { businessAreas, gaps, focusAreas, │
│ excerpts, formUrl?, ... } │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────── Phase A.5 (sonnet 推奨) ─────────────────┐
│ message-builder.cjs │
│ buildMessagePrompt(analysis) ───► CLI に渡すテキスト │
│ CLI (Claude/Codex/Gemini) ───► 最終本文を生成 │
│ buildCustomMessage(analysis) ───► フォールバック │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────── Phase B (順次, sonnet) ──────────────────┐
│ MCP Playwright で実ブラウザにフォーム入力 │
│ → スクショ → awaiting_approval ログ │
└────────────────────────────────────────────────────────┘3. analysis オブジェクトの構造
parallel-analysis.cjs が返す analysis オブジェクトは、Phase A.5 の唯一の入力です。 プロンプトに何が渡るかを理解すると、設定の効き方が読みやすくなります。
type Analysis = {
// ── 基本情報 ───────────────────────────────
companyName: string; // 会社名 (ターゲットリストから)
companyUrl?: string; // 公式サイト URL (空ならフォーム探索が走る)
formUrl?: string; // 既知のフォーム URL
// ── 事業領域の抽出結果 ─────────────────────
businessAreas: string[]; // 例: ["CMS構築", "EC構築", "DX支援"]
focusAreas: string[]; // 例: ["Sitecore", "Adobe Experience Manager"]
keywords: string[]; // テキスト頻出語のトップ N
// ── ギャップ分析 ──────────────────────────
// 設定の valuePropositions.strengths と相手企業の業務領域から
// 「相手にとって価値が出そうな」テーマを抽出
gaps: Array<{
strength: string; // 自社の強み (settings.json 由来)
rationale: string; // なぜ相手に刺さりそうか
confidence: 'high' | 'mid' | 'low';
}>;
// ── サイト抜粋 (生のテキスト引用) ──────────
excerpts: Array<{
page: 'top' | 'service' | 'about' | 'news' | 'recruit';
text: string; // ~200 文字程度の生テキスト
}>;
// ── サンプル/コンプライアンス情報 ──────────
isSalesNg: boolean; // 営業 NG 文言の検出
hasContactForm: boolean; // 問い合わせフォームの存在
captchaDetected?: 'recaptcha-v2' | 'recaptcha-v3' | 'hcaptcha' | 'turnstile';
};4. buildMessagePrompt の出力構造
buildMessagePrompt(analysis) は、以下のセクションを順番に持つテキストプロンプトを返します。 CLI はこのプロンプトを「そのまま」受け取って本文を生成します。
# CONTEXT
- 会社名 / 公式 URL / 既知のフォーム URL
- 事業領域 / 注力分野 (analysis.businessAreas, focusAreas)
- 自社プロフィール (companyProfile)
# APPROACH OBJECTIVE
${messageTemplates.approachObjective}
(例: "CMS 構築・リニューアルの相談窓口として認知してもらう")
# APPROACH GUARDRAILS
${messageTemplates.approachGuardrails}
(例: "価格や競合比較は書かない。相手の課題を決めつけない")
# GAP ANALYSIS
- analysis.gaps から confidence:high のみ最大 3 件
- 「自社の強み × 相手の業務領域」のペアごとに rationale を提示
# SITE EXCERPTS
- analysis.excerpts のうち最も関連度の高い 1-2 件
- 「貴社の◯◯事業を拝見し…」という導入の根拠
# CONSTRAINTS
- 400〜600 文字
- 自社紹介から入らない、相手の事業から入る
- テンプレート感を排除する
- Compliance Footer は自動付与されるので本文には含めない
# OUTPUT
プレーンテキストの問い合わせ本文のみ (Markdown 禁止)ポイントは APPROACH OBJECTIVE と APPROACH GUARDRAILS がそのまま CLI の 制約として反映されることです。設定で「やりたいこと」「やってはいけないこと」を 1〜2 行ずつ書くだけで、 全社共通の文体ガイドが効くようになります。
5. メッセージ作成 8 原則
これらは approachObjective / approachGuardrails 経由で CLI に伝わります。
- 相手の事業から入る — 自社紹介は最後にする。書き出しは「貴社の◯◯事業を拝見し」
- この会社だけに書いた感を出す — analysis.excerpts から 1 件は必ず引用する
- 尖った強みに集中 — 全部伝えようとしない。
gapsから high 1〜2 件のみ - Win-Win は匂わせ程度 — 「お互いに〜」は使わない、相手のメリットだけを書く
- 実績は控えめに — 企業名より数字 (「190 件超」のような表現)
- 相手の課題を決めつけない — 「〜とお見受けします」「もしご検討の余地があれば」
- 提供価値を前面に — 「相手に何ができるか」を先に書き、相談を促すクロージング
- コンパクトに — 400〜600 文字、読了 1〜2 分。改行多めで視認性確保
6. 設定リファレンス
data/settings.json の以下のキーが直接プロンプトに作用します。 ダッシュボードの Settings タブ から GUI でも編集可能です。
{
"companyProfile": {
"companyName": "株式会社サンプル",
"contactName": "中澤 圭志",
"email": "contact@example.com",
"phone": "070-XXXX-XXXX"
},
"messageTemplates": {
"approachObjective": "CMS 構築・リニューアルの相談窓口として認知してもらう",
"approachGuardrails": "価格や競合比較は書かない。相手の課題を決めつけない",
"tone": "professional",
"signature": "{name}\n{company}\nTEL: {phone}"
},
"valuePropositions": {
"strengths": [
"Sitecore 国内導入実績 No.1",
"CMS 構築 190 件超",
"Adobe Experience Manager 連携"
],
"successPatterns": [
"金融 SI の社内 DX 部門と CMS 刷新を共同推進",
"製造業のグローバル CMS 統合"
],
"industryProfiles": {
"manufacturing": "製造業向けテンプレ…",
"saas": "SaaS 向けテンプレ…"
}
},
"preferences": {
"complianceFooter": true,
"minBodyChars": 400,
"maxBodyChars": 600
}
}valuePropositions.strengths— ギャップ分析の素材。3〜8 件が経験上ベスト。valuePropositions.successPatterns— 「直接の自慢」ではなく「協業の文脈」として CLI が間接的に引用。valuePropositions.industryProfiles— フォールバック生成時の業種別テンプレ。preferences.minBodyChars/maxBodyChars— 字数レンジ。範囲外は再生成 or トリム。
7. Good / Bad の対比例
同じ analysis から、原則を守った場合と守らなかった場合の差を示します。
❌ Bad — 自社紹介から入る・テンプレ感
株式会社サンプルの中澤と申します。 弊社は Sitecore 国内導入実績 No.1 の CMS ベンダーで、190 件超の構築実績がございます。 御社の事業に弊社のサービスは必ずお役に立てます。ぜひお打ち合わせの機会をいただけませんでしょうか。
✅ Good — 相手の事業から入る・引用付き
貴社のグローバル製造拠点向けの CMS 統合の取り組みを拝見し、ご連絡いたしました。 特に「多言語サイトの運用負荷削減」をテーマに掲げられている点が印象に残っています。 私どもは Sitecore を中心に CMS 構築を 190 件ほどお手伝いしており、 製造業のグローバル統合 CMS のプロジェクトでは、地域ごとの編集権限と 公開フローの分離をテンプレ化したことで、運用工数を 4〜6 割削減できた事例があります。 もし「グローバル CMS 統合」のテーマでヒアリングや事例共有の場をお取りいただけそうでしたら、 30 分ほどのオンライン打ち合わせでも構いません。お気軽にご検討ください。
8. AI CLI ごとの特性
| CLI | 強み | 注意点 | 推奨用途 |
|---|---|---|---|
| Claude Code | 日本語の文章品質、トーン制御、長文の構成 | API コストは中〜高 (sonnet 4.6 / 4.7) | BtoB 営業文面、品質最優先 |
| Codex CLI | 低コスト、構造化指示への素直な追従 | 感情面・抑揚は Claude より弱い | 大量送信、テンプレ強め運用 |
| Gemini CLI | Google 検索連携、グラウンディング | 日本語表現に個性が出やすい (要 guardrails) | 業界調査込みで文面化したい時 |
AI CLI セットアップのページで、それぞれのインストールと API キー 設定の手順を解説しています。
9. フォールバック動作
CLI 呼び出しが以下のいずれかになった場合、buildCustomMessage(analysis) のテンプレ生成に自動フォールバックします。
- CLI のプロセス起動に失敗 (PATH 不在 / 認証エラー)
- 30 秒以内に応答が無い (タイムアウト)
- 生成された本文が
minBodyCharsを下回る /maxBodyCharsを上回り、リライトも失敗 - 本文に placeholder 文言 (例: 「【URL 不在のため CLI 本体が最終化します】」) が残っている
フォールバック時は valuePropositions.industryProfiles の業種別テンプレが選択され、{companyName} / {strengths} / {contactName} 等の 変数が埋め込まれます。テンプレ感は強くなりますが、最低限の品質は保たれます。
10. Compliance Footer
preferences.complianceFooter: true (デフォルト) の場合、特定電子メール法第 4 条の 4 要件 (送信元会社名・氏名・連絡先メール・オプトアウト案内) が 本文中に揃っているかを src/compliance.cjs がチェックします。
不足要素のみ自動追記される実装になっており、以下のような文末ブロックが必要に応じて生成されます:
────────────────────────────
株式会社サンプル 中澤 圭志
TEL: 070-XXXX-XXXX
Email: contact@example.com
今後のご連絡が不要な場合は、お手数ですが本メールへのご返信にてお知らせください。API: POST /api/compliance/check でも単体検証が可能で、レスポンスに { status: 'ok' | 'warn' | 'fail', missing: [...] } が返ります。
11. トラブルシューティング
| 症状 | 原因の典型 | 対処 |
|---|---|---|
| 生成本文がテンプレ感強い | analysis.excerpts が空 / 短すぎる | 公式サイトのトップ/サービスページが薄いケース。companyUrl を見直し、必要なら「会社名 + 公式」検索で代替 URL を探す |
| 「弊社は」「弊社の強みは」で始まる | approachGuardrails に「自社紹介から始めない」を入れていない | guardrails に明文化し、CLI モデルを sonnet 以上に |
| 文字数オーバー | maxBodyChars 未設定 or 大きすぎる | 500〜700 が経験的に最適。Claude/Codex とも 1000 を超えると冗長化しやすい |
| 署名が二重に入る | CLI が本文末尾に署名 + Compliance Footer も自動追記 | guardrails に「署名は付けない (Compliance Footer 自動付与)」を追記 |
422 で awaiting_approval が拒否される | details.sentMessage が欠如 or 30 文字未満 | 1.2.100+ の API ガード仕様。フォーム本文欄に実入力した文字列を sentMessage として渡す |
12. 関連ドキュメント
- ワークフロー — Phase A / A.5 / B の全体像
- 設定リファレンス — settings.json の全フィールド
- AI CLI セットアップ — Claude/Codex/Gemini のインストール
- リストビルダー — メッセージ送信対象企業の発見・選定
- トラブルシューティング — 一般的な不具合と対処