なぜメールの自動分類が必要なのか
毎日大量に届くメールの中から、緊急の案件・対応が必要な依頼・情報共有だけのメールを瞬時に見分けるのは、想像以上に認知負荷のかかる作業です。Gemini API の Function Calling と構造化出力を組み合わせれば、受信メールを自動的に分類し、優先度付けと要約を行うインテリジェントなシステムを構築できます。
これは Gemini Lab のプレミアム記事で扱うような実践的なシステム構築の一部を、無料で体験いただける見本記事です。より深い内容にご興味をお持ちの方は、ぜひメンバーシップもご検討ください。
システムアーキテクチャの設計
メール分類システムは、以下の3つのステージで構成します。
ステージ1: 分類(Classification) — メールのカテゴリと優先度を判定
ステージ2: 要約(Summarization) — 本文の要点を3文以内に抽出
ステージ3: アクション抽出(Action Extraction) — 返信や対応が必要な場合にアクションアイテムを生成
// types/email.ts
export interface EmailInput {
id: string;
from: string;
to: string;
subject: string;
body: string;
receivedAt: string;
}
export type EmailCategory =
| "urgent" // 緊急対応が必要
| "action" // 対応が必要(緊急ではない)
| "info" // 情報共有・FYI
| "newsletter" // ニュースレター・マーケティング
| "spam"; // スパム・不要
export type Priority = "high" | "medium" | "low";
export interface ClassifiedEmail extends EmailInput {
category: EmailCategory;
priority: Priority;
summary: string;
actionItems: string[];
confidence: number; // 分類の確信度(0-1)
}Function Calling でメールを分類する
Gemini の Function Calling を使うことで、メール分類の出力スキーマを厳密に定義できます。これにより、後続の処理で型安全にデータを扱えます。
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI("YOUR_GEMINI_API_KEY");
// メール分類ツールの定義
const classifyEmailTool = {
functionDeclarations: [{
name: "classify_email",
description: "受信メールを分析し、カテゴリ・優先度・要約・アクションアイテムを返す",
parameters: {
type: "object",
properties: {
category: {
type: "string",
enum: ["urgent", "action", "info", "newsletter", "spam"],
description: "メールのカテゴリ",
},
priority: {
type: "string",
enum: ["high", "medium", "low"],
description: "優先度",
},
summary: {
type: "string",
description: "メール本文の要約(3文以内、日本語)",
},
action_items: {
type: "array",
items: { type: "string" },
description: "必要なアクションのリスト(対応不要なら空配列)",
},
confidence: {
type: "number",
description: "分類の確信度(0.0〜1.0)",
},
},
required: ["category", "priority", "summary", "action_items", "confidence"],
},
}],
};
async function classifyEmail(email: EmailInput): Promise<ClassifiedEmail> {
const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash",
tools: [classifyEmailTool],
});
const prompt = `以下のメールを分析してください。
差出人: ${email.from}
件名: ${email.subject}
本文:
${email.body}
このメールのカテゴリ、優先度、要約、必要なアクションを classify_email 関数で返してください。`;
const result = await model.generateContent(prompt);
const response = result.response;
// Function Call の結果を取得
const functionCall = response.candidates?.[0]?.content?.parts?.find(
(part) => part.functionCall
)?.functionCall;
if (!functionCall || functionCall.name !== "classify_email") {
throw new Error("Expected classify_email function call");
}
const args = functionCall.args as {
category: EmailCategory;
priority: Priority;
summary: string;
action_items: string[];
confidence: number;
};
return {
...email,
category: args.category,
priority: args.priority,
summary: args.summary,
actionItems: args.action_items,
confidence: args.confidence,
};
}functionDeclarations でスキーマを定義しておくことで、Gemini はこの構造に準拠したデータを返します。enum でカテゴリの選択肢を制限しているため、想定外の値が返ることはありません。
バッチ処理による大量メールの効率的分類
メールボックスに数百通のメールがある場合、1通ずつ処理すると時間がかかります。並行処理とレート制限を組み合わせたバッチ処理を実装します。
// utils/batch-classifier.ts
// 同時実行数を制限するセマフォ
class Semaphore {
private queue: (() => void)[] = [];
private running = 0;
constructor(private max: number) {}
async acquire(): Promise<void> {
if (this.running < this.max) {
this.running++;
return;
}
return new Promise<void>((resolve) => {
this.queue.push(resolve);
});
}
release(): void {
this.running--;
const next = this.queue.shift();
if (next) {
this.running++;
next();
}
}
}
async function batchClassify(
emails: EmailInput[],
concurrency = 5
): Promise<ClassifiedEmail[]> {
const semaphore = new Semaphore(concurrency);
const results: ClassifiedEmail[] = [];
const errors: Array<{ email: EmailInput; error: string }> = [];
const tasks = emails.map(async (email) => {
await semaphore.acquire();
try {
const classified = await classifyEmail(email);
results.push(classified);
} catch (error) {
errors.push({
email,
error: error instanceof Error ? error.message : "Unknown error",
});
} finally {
semaphore.release();
}
});
await Promise.all(tasks);
if (errors.length > 0) {
console.warn(`${errors.length}/${emails.length} emails failed classification`);
}
// 優先度でソート(high → medium → low)
const priorityOrder = { high: 0, medium: 1, low: 2 };
results.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
return results;
}分類ルールのカスタマイズ
ビジネスの種類や個人の作業スタイルに合わせて、分類ルールをカスタマイズするための設定レイヤーを追加します。
// config/classification-rules.ts
interface ClassificationRules {
urgentSenders: string[]; // 常に urgent として扱う送信者
infoOnlyDomains: string[]; // 常に info として扱うドメイン
spamKeywords: string[]; // スパムと判定するキーワード
customPromptRules: string; // 追加の分類ルール(自然言語)
}
const defaultRules: ClassificationRules = {
urgentSenders: [
"boss@company.com",
"ceo@company.com",
],
infoOnlyDomains: [
"noreply@github.com",
"notifications@slack.com",
],
spamKeywords: [
"unsubscribe",
"limited time offer",
],
customPromptRules: `
- クライアントからの請求関連メールは常に "urgent" + "high" にする
- 社内の週報は "info" + "low" にする
- 採用関連のメールは "action" + "medium" にする
`,
};
function buildClassificationPrompt(
email: EmailInput,
rules: ClassificationRules
): string {
// 事前ルールチェック
if (rules.urgentSenders.includes(email.from)) {
return `このメールは重要な送信者(${email.from})からです。カテゴリを "urgent" として分類してください。\n\n${email.body}`;
}
const domain = email.from.split("@")[1];
if (domain && rules.infoOnlyDomains.includes(domain)) {
return `このメールは通知ドメイン(${domain})からです。カテゴリを "info" として分類してください。\n\n${email.body}`;
}
return `以下の追加ルールに従ってメールを分類してください:
${rules.customPromptRules}
差出人: ${email.from}
件名: ${email.subject}
本文: ${email.body}`;
}分類結果のダッシュボード出力
分類結果をわかりやすく表示するダッシュボード用のレポートを生成します。
function generateReport(emails: ClassifiedEmail[]): string {
const byCategory = emails.reduce((acc, email) => {
acc[email.category] = (acc[email.category] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const urgent = emails.filter(e => e.category === "urgent");
const actions = emails.filter(e => e.category === "action");
let report = `📊 メール分類レポート\n`;
report += `━━━━━━━━━━━━━━━━━━━━\n`;
report += `総数: ${emails.length}通\n\n`;
// カテゴリ別集計
Object.entries(byCategory).forEach(([cat, count]) => {
const icon = { urgent: "🔴", action: "🟡", info: "🔵", newsletter: "📰", spam: "⛔" }[cat] || "⚪";
report += `${icon} ${cat}: ${count}通\n`;
});
// 緊急メール一覧
if (urgent.length > 0) {
report += `\n🔴 緊急対応が必要:\n`;
urgent.forEach(e => {
report += ` - [${e.from}] ${e.subject}\n ${e.summary}\n`;
});
}
// アクション一覧
if (actions.length > 0) {
report += `\n🟡 対応が必要:\n`;
actions.forEach(e => {
report += ` - [${e.from}] ${e.subject}\n`;
e.actionItems.forEach(item => {
report += ` → ${item}\n`;
});
});
}
return report;
}
// 使用例
const classified = await batchClassify(inboxEmails);
console.log(generateReport(classified));
// 期待出力:
// 📊 メール分類レポート
// ━━━━━━━━━━━━━━━━━━━━
// 総数: 50通
//
// 🔴 urgent: 3通
// 🟡 action: 12通
// 🔵 info: 20通
// 📰 newsletter: 10通
// ⛔ spam: 5通
//
// 🔴 緊急対応が必要:
// - [client@example.com] 納品日の変更について
// クライアントから納品日を3日前倒しする依頼。今週金曜までに回答が必要。全体を振り返って
Gemini API の Function Calling を使えば、受信メールを自動的に分類・要約・アクション抽出するシステムを効率よく構築できます。スキーマを厳密に定義することで型安全なデータフローが実現でき、カスタムルールの追加でビジネスニーズに柔軟に対応できる設計です。
AI を使った業務自動化の全体像
Gemini API の基本は「Gemini API クイックスタート」を、Function Calling の詳細は「Function Calling完全ガイド」もあわせてご覧ください。ストリーミング応答の実装に興味がある方は「ストリーミング応答とマルチターン会話の実装」も参考になります。
個人開発12年の視点で見る Gemini 運用
運用時に毎月見ているメトリクス
- API 呼び出し成功率と平均レイテンシ
- 1リクエストあたりの平均トークン消費
- AdMob 売上との比較で見た ROI