0. はじめに
こんな作業、手でやっていませんか?
Gmailに届いた案件メールを開く → 内容を読む → クライアント名、案件名、実施月などを手作業でスプレッドシートに転記する → 次のメール…
問題: 転記ミスが起きる / 担当者が休むと止まる / 毎日30分〜1時間がこの作業に消える / メールが多いと追いつかない
これ、GAS(Google Apps Script) × Gemini API で自動化できます。しかも Geminiの無料枠 で。
この記事では、「Gmailに届いたメールをAIで自動解析 → スプレッドシートに記録」する仕組みを、STEP形式でゼロから構築していきます。
この記事はClaudeと一緒に書きました。GASは触ったことがある・AI APIは初めてという方を想定しています。
本記事で扱う内容
| # | トピック | ひとこと | セクション |
|---|---|---|---|
| 1 | 完成イメージ | 何ができるようになるか | 1. 完成イメージ |
| 2 | 事前準備 | APIキー取得・シート準備 | 2. 事前準備 |
| 3〜5 | STEP 1〜3: 基盤構築 | シート設計・GAS作成・メール取得 | 3. STEP 1: スプレッドシート準備 |
| 6〜7 | STEP 4: Gemini API | APIの叩き方とプロンプト設計 | 6. STEP 4: Gemini APIを叩く |
| 8〜9 | STEP 5〜6: 書き込みと分類 | シート記録・メール分類 | 8. STEP 5: スプレッドシートに書き込む |
| 10 | 全体コード | コピペで動く完成版 | 10. 全体コード |
| 11〜12 | デバッグと本番化 | 動作確認・トリガー設定 | 11. 動作確認とデバッグ |
| 13〜16 | 応用・注意点 | コスト・セキュリティ・比較 | 13. やりがちな失敗 |
| 17 | まとめ | 要点とBefore/After | 17. まとめ |
それでは、まず完成イメージから見ていきましょう。
1. 完成イメージ — 何ができるようになるか
一言で言うと
Gmailにメールが届くだけで、AIが中身を読み取り、スプレッドシートに自動で整理してくれる
処理の全体フロー
Gmail受信 → GASが定期検知 → Gemini APIで解析 → スプレッドシートに自動記録
Before / After
| Before(手作業) | After(GAS × Gemini) | |
|---|---|---|
| 転記作業 | メールを開いて目視でコピペ | 自動で抽出・記録 |
| 所要時間 | 1通あたり3〜5分 | 数秒(API応答時間のみ) |
| 転記ミス | 人間だから起きる | JSONスキーマで構造化、ミスなし |
| 担当者不在 | 止まる | 止まらない(トリガーで自動実行) |
| 分類・要約 | 手動で判断 | AIが自動分類・要約 |
| コスト | 人件費(月数万円相当) | Gemini無料枠で0円 |
2. 事前準備 — 必要なもの一覧
| 必要なもの | 準備方法 |
|---|---|
| Googleアカウント | 個人アカウントでOK。Workspace環境でも可 |
| Gemini APIキー | Google AI Studio で無料発行 |
| Googleスプレッドシート | 新規作成(次のSTEPで列構成を設計) |
Gemini APIキーの取得手順
- Google AI Studio にアクセス
- Googleアカウントでログイン → 利用規約に同意
- 左メニューの「Get API key」をクリック
- 「Create API key」でキーを発行
- 発行されたキーをコピーして安全な場所に保存
Gemini APIはクレジットカード不要で無料枠を利用できます。個人アカウントであれば通常すぐに発行可能です。
Workspace(組織)アカウントの場合、管理者設定によってはAPI利用が制限されていることがあります。発行できない場合は組織の管理者に確認してください。
3. STEP 1: スプレッドシートを準備する
まずは、抽出結果を記録するスプレッドシートを作成します。Googleドライブから新規スプレッドシートを開き、以下の列構成で1行目にヘッダーを入力してください。
| 列 | ヘッダー名 | 内容 |
|---|---|---|
| A | 受信日時 | メールの受信日時 |
| B | 差出人 | メールの送信者 |
| C | クライアント名 | AIが抽出 |
| D | 案件名 | AIが抽出 |
| E | 投稿条件 | AIが抽出 |
| F | 実施月 | AIが抽出 |
| G | 要約 | AIが要約 |
| H | 分類 | AIが分類(案件依頼/見積もり/問い合わせ/その他) |
| I | メール全文 | 原本保持(後述) |
I列の「メール全文」は必ず残してください。 AIの抽出結果にハルシネーション(事実と異なる出力)がないか、後から原本で検証できるようにするためです。
4. STEP 2: GASプロジェクトを作成する
スプレッドシートのメニューから「拡張機能」→「Apps Script」を開き、エディタを起動します。
まずは動作確認として、最小限のコードを実行してみましょう。
function hello() {
Logger.log("GASが動きました!");
}
エディタ上部の「▶ 実行」を押し、実行ログに「GASが動きました!」と表示されればOKです。初回実行時はGmailやスプレッドシートへのアクセス許可を求められるので、許可してください。
5. STEP 3: Gmailからメールを取得する
次に、GASでGmailのメールを取得します。
function getLatestEmail() {
// デバッグ用: 既読・未読問わず最新1件を取得
const threads = GmailApp.search("label:INBOX", 0, 1);
// 本番用: 未読メールのみ取得する場合は上の行を下に置き換える
// const threads = GmailApp.search("is:unread label:INBOX", 0, 1);
if (threads.length === 0) {
Logger.log("対象メールなし");
return;
}
const messages = threads[0].getMessages();
const latest = messages[messages.length - 1];
Logger.log("件名: " + latest.getSubject());
Logger.log("差出人: " + latest.getFrom());
Logger.log("本文: " + latest.getPlainBody());
}
テスト段階では label:INBOX (全メール)で動作確認し、本番では is:unread label:INBOX (未読のみ)に切り替えてください。 全メールを対象にすると、過去のメールまで繰り返し処理されてしまいます。
6. STEP 4: Gemini APIを叩く
ここが本記事の核心です。GASからGemini APIを呼び出してメール本文を解析します。
APIキーの安全な管理
APIキーをコードに直書きするのはNGです。スクリプトプロパティに保存しましょう。
- Apps Scriptエディタの左メニュー → 「⚙ プロジェクトの設定」
- 「スクリプトプロパティ」セクションで「プロパティを追加」
- プロパティ名:
GEMINI_API_KEY、値: 取得したAPIキーを入力
Gemini APIを呼び出すコード
function callGemini(emailBody) {
// スクリプトプロパティからAPIキーを安全に取得
const apiKey = PropertiesService.getScriptProperties()
.getProperty("GEMINI_API_KEY");
// Gemini 2.0 Flash(高速・無料枠あり)
const model = "gemini-2.0-flash";
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
// プロンプト: JSON形式で抽出・要約・分類を指示
const prompt = `
あなたはメール解析アシスタントです。
以下のメール本文から情報を抽出し、**必ず以下のJSON形式のみ**で回答してください。
JSON以外のテキスト(説明文、マークダウンの装飾など)は一切含めないでください。
{
"client": "クライアント名(不明なら'不明')",
"project": "案件名(不明なら'不明')",
"conditions": "投稿条件(不明なら'不明')",
"month": "実施月(不明なら'不明')",
"summary": "メール全体の要約(2〜3文)",
"category": "以下から1つ選択: 案件依頼 / 見積もり / 問い合わせ / その他"
}
--- メール本文 ---
${emailBody}
`;
const payload = {
contents: [{ parts: [{ text: prompt }] }]
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true // エラー時もレスポンスを取得
};
const response = UrlFetchApp.fetch(url, options);
const json = JSON.parse(response.getContentText());
// Geminiの回答テキストを取得
const rawText = json.candidates[0].content.parts[0].text;
// マークダウンの ```json ... ``` を除去してクリーンなJSONにする
const cleanText = rawText
.replace(/```json\s*/gi, "")
.replace(/```\s*/g, "")
.trim();
return JSON.parse(cleanText);
}
Geminiは回答を ```json ... ``` で囲んで返すことがあります。この除去処理を忘れると JSON.parse でエラーになるのが最もよくあるハマりポイントです。
7. プロンプト設計のコツ
STEP 4のプロンプトがなぜあの形なのか、設計のポイントを解説します。
良いプロンプト / 悪いプロンプト
| 悪い例 | 良い例 | |
|---|---|---|
| 出力形式 | 「情報を抽出して」(形式を指定していない) | 「以下のJSON形式のみで回答」(スキーマを明示) |
| 不明時の処理 | 指定なし(AIが勝手に推測してしまう) | 「不明なら'不明'と出力」(ハルシネーション防止) |
| 分類のカテゴリ | 「適切に分類して」(曖昧) | 「以下から1つ選択: 案件依頼 / 見積もり / 問い合わせ / その他」(選択肢を限定) |
ポイント3つ
- JSONスキーマを例示する — Geminiに「この形で返して」と見本を見せるのが最も確実
- 「不明なら'不明'」を明示する — これがないとAIが勝手に推測して嘘の情報を出力する
- 分類カテゴリは選択式にする — 自由記述だと表記ゆれが起き、スプレッドシートでフィルタしにくい
8. STEP 5: スプレッドシートに書き込む
Geminiの解析結果をスプレッドシートに1行追加する関数です。
function appendToSheet(data, message) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("シート1");
if (!sheet) {
Logger.log("エラー: 「シート1」が見つかりません");
return;
}
sheet.appendRow([
message.getDate(), // A列: 受信日時
message.getFrom(), // B列: 差出人
data.client || "不明", // C列: クライアント名
data.project || "不明", // D列: 案件名
data.conditions || "不明", // E列: 投稿条件
data.month || "不明", // F列: 実施月
data.summary || "", // G列: 要約
data.category || "その他", // H列: 分類
message.getPlainBody() // I列: メール全文(原本)
]);
}
9. STEP 6: メール分類の活用
STEP 4のプロンプトで category フィールドを含めたことで、メールが自動分類されます。
| 分類 | 説明 | 使い方の例 |
|---|---|---|
| 案件依頼 | 新規案件の依頼メール | 案件管理ボードに自動追加 |
| 見積もり | 金額に関する問い合わせ | 経理チームに転送 |
| 問い合わせ | 一般的な質問 | FAQで対応可能か判断 |
| その他 | 上記に当てはまらない | 手動確認キューへ |
カテゴリは自分の業務に合わせて変更してください。プロンプト内の選択肢を書き換えるだけです。
10. ここまでの全体コード
STEP 1〜6を統合したコピペで動く完成版です。
// ============================================================
// Gmail × Gemini API メール自動解析スクリプト
// 概要: Gmailの未読メールをGemini APIで解析し、
// スプレッドシートに自動で追記する
// ============================================================
// 使用するGeminiモデル
const GEMINI_MODEL = "gemini-2.0-flash";
// 書き込み先のシート名
const SHEET_NAME = "シート1";
// ============================================================
// メイン関数: メール取得 → Gemini解析 → シート書き込み
// ============================================================
function main() {
// デバッグ用: 既読・未読問わず最新1件を取得
const threads = GmailApp.search("label:INBOX", 0, 1);
// 本番用: 未読メールのみ対象にする場合は上をコメントアウトし下を有効化
// const threads = GmailApp.search("is:unread label:INBOX", 0, 5);
if (threads.length === 0) {
Logger.log("対象のメールが見つかりませんでした。");
return;
}
for (const thread of threads) {
const messages = thread.getMessages();
const latest = messages[messages.length - 1];
const body = latest.getPlainBody();
Logger.log("--- 処理中: " + latest.getSubject() + " ---");
// Gemini APIで解析
const parsed = callGemini(body);
if (!parsed) {
Logger.log("解析失敗。スキップします。");
continue;
}
// スプレッドシートに書き込み
appendToSheet(parsed, latest);
Logger.log("書き込み完了: " + (parsed.project || "不明"));
// 本番用: 処理済みメールを既読にする
// thread.markRead();
}
}
// ============================================================
// Gemini APIを呼び出してメール本文を解析する関数
// ============================================================
function callGemini(emailBody) {
const apiKey = PropertiesService.getScriptProperties()
.getProperty("GEMINI_API_KEY");
if (!apiKey) {
Logger.log("エラー: GEMINI_API_KEYが設定されていません。");
return null;
}
const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${apiKey}`;
const prompt = `
あなたはメール解析アシスタントです。
以下のメール本文から情報を抽出し、**必ず以下のJSON形式のみ**で回答してください。
JSON以外のテキスト(説明文、マークダウンの装飾など)は一切含めないでください。
{
"client": "クライアント名(不明なら'不明')",
"project": "案件名(不明なら'不明')",
"conditions": "投稿条件(不明なら'不明')",
"month": "実施月(不明なら'不明')",
"summary": "メール全体の要約(2〜3文)",
"category": "以下から1つ選択: 案件依頼 / 見積もり / 問い合わせ / その他"
}
--- メール本文 ---
${emailBody}
`;
const payload = {
contents: [{ parts: [{ text: prompt }] }]
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
if (statusCode !== 200) {
Logger.log("APIエラー (HTTP " + statusCode + "): " + response.getContentText());
return null;
}
const json = JSON.parse(response.getContentText());
const rawText = json.candidates[0].content.parts[0].text;
Logger.log("Gemini応答: " + rawText);
// ```json ... ``` の除去
const cleanText = rawText
.replace(/```json\s*/gi, "")
.replace(/```\s*/g, "")
.trim();
return JSON.parse(cleanText);
} catch (error) {
Logger.log("Gemini API呼び出しエラー: " + error.message);
return null;
}
}
// ============================================================
// スプレッドシートに1行追記する関数
// ============================================================
function appendToSheet(data, message) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(SHEET_NAME);
if (!sheet) {
Logger.log("エラー: シート「" + SHEET_NAME + "」が見つかりません。");
return;
}
sheet.appendRow([
message.getDate(), // A列: 受信日時
message.getFrom(), // B列: 差出人
data.client || "不明", // C列: クライアント名
data.project || "不明", // D列: 案件名
data.conditions || "不明", // E列: 投稿条件
data.month || "不明", // F列: 実施月
data.summary || "", // G列: 要約
data.category || "その他", // H列: 分類
message.getPlainBody() // I列: メール全文(原本)
]);
}
11. 動作確認とデバッグ
実行方法
- Apps Scriptエディタで
main関数を選択 - 「▶ 実行」をクリック
- 実行ログ(Ctrl+Enter)でGeminiの応答と書き込み結果を確認
よくあるエラーと対処法
| エラー | 原因 | 対処 |
|---|---|---|
GEMINI_API_KEYが設定されていません |
スクリプトプロパティ未設定 | プロジェクト設定 → スクリプトプロパティに追加 |
APIエラー (HTTP 400) |
プロンプトが不正 or モデル名のタイポ | モデル名を gemini-2.0-flash に確認 |
APIエラー (HTTP 429) |
レートリミット超過 | 数分待って再実行。本番では Utilities.sleep(2000) を挿入 |
JSON.parse でエラー |
```json ``` の除去漏れ or Geminiが不正なJSONを返した |
Logger.logで生のレスポンスを確認。プロンプトを調整 |
シートが見つかりません |
シート名の不一致 |
SHEET_NAME の値とシート名が一致しているか確認 |
12. 本番運用に切り替える
デバッグが通ったら、本番運用に切り替えます。コードの変更は3箇所だけです。
変更1: 未読メールのみを対象にする
// 変更前(デバッグ用)
const threads = GmailApp.search("label:INBOX", 0, 1);
// 変更後(本番用)
const threads = GmailApp.search("is:unread label:INBOX", 0, 5);
変更2: 処理済みメールを既読にする
// コメントアウトを外す
thread.markRead();
変更3: 時間ベーストリガーを設定する
- Apps Scriptエディタの左メニュー → 「⏰ トリガー」
- 「トリガーを追加」をクリック
- 実行する関数:
main - イベントのソース: 「時間主導型」
- 時間ベースのトリガーのタイプ: 「分ベースのタイマー」→ 「5分おき」
- 保存
これで5分おきにGmailをチェックし、未読メールがあれば自動で解析・記録されます。
レートリミットに注意。 Gemini 2.0 Flashの無料枠は1分あたり10〜15リクエスト程度です。大量のメールを一度に処理する場合は、ループ内に Utilities.sleep(2000) を挿入してください。
13. やりがちな失敗(アンチパターン)
失敗1: プロンプトでJSON形式を強制していない
「メールから情報を抽出して」だけだと、Geminiが自然言語で回答してしまいJSONパースできません。必ずスキーマを例示してください。
失敗2: ```json ``` の除去を忘れている
Geminiの応答は高確率でマークダウンのコードブロックで囲まれます。これを除去せずに JSON.parse すると必ずエラーになります。
失敗3: メール全文を保存していない
AIの抽出結果が正しいかどうか、後から検証する手段がなくなります。I列に原本を必ず残しましょう。
失敗4: APIキーをコードに直書きしている
ソースを共有したりGitにコミットした瞬間にAPIキーが漏洩します。必ずスクリプトプロパティを使ってください。
14. コストの話 — Gemini APIは実質無料?
Gemini 2.0 Flash の料金(2026年3月時点)
| 項目 | 無料枠 | 有料枠 |
|---|---|---|
| 入力 | 無料 | $0.10 / 100万トークン |
| 出力 | 無料 | $0.40 / 100万トークン |
| レートリミット | 10〜15 RPM / 1,000 RPD | 2,000 RPM |
実際にどのくらい処理できる?
メール1通あたりの平均トークン数を約500トークン(入力)+ 200トークン(出力)と仮定すると:
- 無料枠: 1日1,000リクエスト → 1日1,000通のメール処理が可能
- 一般的なビジネスで1日1,000通を超えることは稀
個人利用・小規模チームなら、ほぼ確実に無料枠で収まります。 クレジットカードの登録も不要なので、まずは無料で試してみてください。
参考ドキュメント
15. セキュリティ上の注意点
メール本文をGemini APIに送信するということは、メール内容がGoogleのサーバーに送られるということです。 以下の点を理解した上で利用してください。
注意すべきこと
| 項目 | 対策 |
|---|---|
| 機密情報 | 顧客の個人情報や契約金額が含まれるメールの送信には慎重に |
| 無料枠のデータ利用 | 無料枠では、送信データがGoogleのサービス改善に使用される可能性がある |
| 有料枠のデータ利用 | 有料枠にアップグレードすると、データがサービス改善に使用されない(エンタープライズグレードのプライバシー) |
| APIキー管理 | スクリプトプロパティに保存。コードに直書きしない |
| スクリプト共有 | GASプロジェクトの共有範囲は最小限に |
16. 発展: 他のAI APIとの比較
この記事ではGemini APIを使いましたが、同じアーキテクチャで他のAI APIにも差し替え可能です。
| 比較項目 | Gemini 2.0 Flash | Claude (Haiku) | GPT-4o mini |
|---|---|---|---|
| GASからの呼びやすさ | UrlFetchAppだけでOK | UrlFetchAppだけでOK | UrlFetchAppだけでOK |
| 無料枠 | あり(1,000 RPD) | なし | なし |
| 入力コスト(有料) | $0.10 / 100万トークン | $0.25 / 100万トークン | $0.15 / 100万トークン |
| JSON出力の安定性 | 良い | 良い | 良い |
| Google連携の親和性 | 最高(同じGoogle基盤) | 普通 | 普通 |
GASとの組み合わせならGeminiが最もスムーズです。 Google基盤同士の連携で認証周りがシンプルになります。他のAPIに興味がある場合は、callGemini 関数のURL・ヘッダー・レスポンス形式を差し替えるだけで対応できます。
17. まとめ
要点
| やること | 具体的に |
|---|---|
| GASで構築 | Gmail取得 → Gemini API解析 → スプレッドシート記録 |
| プロンプト設計 | JSONスキーマを明示 + 「不明なら'不明'」 + カテゴリ選択式 |
| 安全なAPI管理 | スクリプトプロパティにAPIキーを保存 |
| 本番化 | 未読フィルタ + markRead + 5分トリガー |
| コスト | Gemini 2.0 Flashの無料枠で十分 |
Before / After
| Before | After | |
|---|---|---|
| メール転記 | 1通3〜5分、手作業 | 自動、数秒 |
| 転記ミス | 人間だから起きる | 構造化JSON、ミスなし |
| 担当者不在 | 業務が止まる | トリガーで自動継続 |
| 分類・要約 | 手動判断 | AI自動分類 |
| 月間コスト | 人件費(数万円相当) | 0円(無料枠) |
最初の一歩
# ① Google AI Studio でAPIキーを取得(無料・カード不要)
https://aistudio.google.com/
# ② スプレッドシートを作成 → 拡張機能 → Apps Script
# ③ セクション10の「全体コード」をコピペ
# ④ スクリプトプロパティにAPIキーを設定
# ⑤ main関数を実行 → スプレッドシートに結果が入る!
まずは1通のメールで試してみてください。 動いたら、トリガーを設定して本番化するだけです。
参考リンク集
| リソース | URL |
|---|---|
| Google AI Studio(APIキー取得) | https://aistudio.google.com/ |
| Gemini API 料金ページ | https://ai.google.dev/gemini-api/docs/pricing |
| Gemini API 公式ドキュメント | https://ai.google.dev/gemini-api/docs |
| GAS GmailApp リファレンス | https://developers.google.com/apps-script/reference/gmail |
| GAS UrlFetchApp リファレンス | https://developers.google.com/apps-script/reference/url-fetch |
ここまで読んでいただきありがとうございます。GAS × Gemini APIの組み合わせは、Google Workspaceユーザーにとって最もローコストで始められるAI自動化の入口です。ぜひ試してみてください。
