営業メールって、正直つらくないですか?
毎日のように届く営業メール、
・数が多い
・検索がしづらい
・欲しい情報が載っていないことも多い
そんな不便さを何とかしたくて、IT業界のエンジニア向け営業メールをAIで字句解析して、DBに保存して検索できるアプリを作ってみました。
表示イメージ
設計
ミッション
営業メールを構造化し、検索可能な形で保存する。
分析対象
対象は「案件メール」と「人材メール」の2種類。
※すべてのメールを解析するとAPIコストがかかるため、Gmailのフィルター機能でラベルが付与されたメールのみを対象とします。
設計上のポイント
- 表現の揺らぎはAIによって吸収
- メール本文と構造化データは分離して保存
- 言語・フレームワークなどの技術キーワードは、マスタ管理により柔軟な検索を実現
サービス選定
メール:Gmail
社内で利用しているため、連携もスムーズ。
AI:ChatGPT
ローカルLLMも試しましたが精度がイマイチだったため、API型を選定。
その中で、精度と価格のバランスから GPT-4.1 miniを採用しました。
解析フォーマット
営業メールは主に「案件紹介」と「人材紹介」の2種類に分けられます。
それぞれ定型的な情報が含まれていますが、表現に揺らぎがあるため抽出はAIに任せます。
フォーマット例
[
{
"メール区分": "案件 or 人材",
"案件名": "Go/AWS なんとか業界の開発案件",
"業務": [
"バックエンド実装",
"インフラ構築"
],
"入場時期・開始時期": [
"2025/06/01",
"2025/07/01"
],
"終了時期": "~長期",
"勤務場所": "東京都",
"単価FROM": 800000,
"単価TO": 900000,
"言語": [
"TypeScript",
"JavaScript",
"PHP"
],
"フレームワーク": [
"React",
"Laravel"
],
"ポジション": [
"PL",
"PM",
"SE",
"PG"
],
"求めるスキル MUST": [],
"求めるスキル WANT": [],
"リモートワーク区分": "フルリモート or リモート可 or 不可",
"リモートワークの頻度": "週一回"
}
]
処理の流れ
- CLI からコマンドを実行して処理を開始
- Gmail API で特定ラベルがついているメールを取得
※ラベルへのメール振り分け設定が完了している想定です - ChatGPT API に投げて要約・構造化
- JSONで受け取り、案件 or 人材に分類
- パースしてDBに保存(メール本文は別途保存)
※(将来的に)フロントで検索インターフェース提供予定
DB設計
検索性を高めるため、emails テーブルに基本情報を格納し、案件/人材情報を子テーブルとして管理する。
言語やフレームワークなどは表現に揺れがあるため、単語を正規化するマスタ設計が必要。
例えば「PHP(Laravel)」のような単語は、言語とフレームワークの両カテゴリに対応できる構造とする。
以上を踏まえるとこのような設計になりました。
※人材情報は今後対応予定です。
フォーマット例
tables:
emails:
role: "全メール共通の基本情報(件名・送信元・本文など)"
relation: []
email_projects:
role: "案件メール専用の詳細情報(単価・勤務地・技術要素など)"
relation:
- emails (1:1)
- entry_timings (1:N)
note: "一覧画面用に技術・業務・ポジションなどをカンマ区切り文字列でも保持(二重管理)"
entry_timings:
role: "案件の入場時期(複数)を正規化管理"
relation: ["email_projects (N:1)"]
keyword_groups:
role: "正規化された技術キーワードのマスタ(PHP、Reactなど)"
relation: ["key_words (1:N)", "email_projects (N:N keyword_group_word_links)"]
keyword_group_word_links:
role: "email_projects と keyword_groups を多対多で結びつける中間テーブル"
relation: ["email_projects (N:1)", "keyword_groups (N:1)"]
key_words:
role: "キーワードの表記ゆれを keyword_groups に紐付ける"
relation: ["keyword_group_word_links (N:1)"]
key_words:
role: "キーワードの表記ゆれを keyword_groups に紐付ける"
relation: ["keyword_groups (N:1)"]
email_keyword_groups:
role: "emails と keyword_groups の多対多中間テーブル(type区分あり)"
relation: ["emails (N:1)", "keyword_groups (N:1)"]
position_groups:
role: "正規化されたポジション名のマスタ(例: PM, PL)"
relation: ["position_words (1:N)", "email_position_groups (1:N)"]
position_words:
role: "ポジションの表記ゆれを position_groups に紐付ける"
relation: ["position_groups (N:1)"]
email_position_groups:
role: "emails と position_groups の多対多中間テーブル"
relation: ["emails (N:1)", "position_groups (N:1)"]
work_type_groups:
role: "正規化された業務種別マスタ(例: バックエンド開発)"
relation: ["work_type_words (1:N)", "email_work_type_groups (1:N)"]
work_type_words:
role: "業務表記ゆれを work_type_groups に紐付ける"
relation: ["work_type_groups (N:1)"]
email_work_type_groups:
role: "emails と work_type_groups の多対多中間テーブル"
relation: ["emails (N:1)", "work_type_groups (N:1)"]
言語選定
ぱぱっと開発したかったのと、ローカル環境の構築が自分の既存リポジトリから流用できたこと、そして学習目的もあって、Go言語を採用しました。
あとから振り返ると、Laravelでフロントも一緒に作ったほうがよかったかも…と少し後悔してます(笑)
今後の対応について
- 人材情報メールへの対応
- フロントの作成
・単語マスターの管理画面
・検索インターフェース
※気になる、既読、非表示などでより選別を簡単にすることも可能
・案件・人材情報に単語を追加で紐づける
ソースコード
※フロントは時間に余裕ができたら作る予定です。