Gmailに届いた依頼をスプレッドシートへ転記していると、入力に時間がかかったり、登録を忘れたりすることがあります。Google Apps Script(GAS)を使うと、対象のメールから日時・送信者・件名などを取得し、一覧へ追加する処理を自動化できます。

この記事では、ラベルを付けた会話を対象に、メールをスプレッドシートへ取り込む方法を紹介します。メールを既読にせず、メッセージIDで登録済みかを確認する構成にし、手動確認から定期実行までの進め方を整理します。

目次

1.今回作る仕組みと、自動化する範囲

GASは、Googleのサービスをプログラムから操作できる仕組みです。今回はGmailから情報を取得し、Googleスプレッドシートに書き込みます。生成AIや外部APIの契約は必要ありません。

ただし、メールを一覧化することと、依頼内容を理解してタスクを完成させることは異なります。担当者・期限・対応の必要性は、人が確認する運用にします。

項目今回の動作
対象Gmailの「task」ラベルに含まれる会話
登録単位対象の会話に含まれるメール1通につき1行
取得する情報メッセージID、日時、送信者、件名、会話へのリンク
対応状況新規登録時は「要確認」。その後は手動で変更する
重複確認登録済みのメッセージIDと照合する
Gmailへの変更既読化、ラベル変更、送信、削除は行わない

「会話」と「メール1通」の違いに注意する

Gmailでは、関連するメールが一つの会話としてまとまります。今回のコードは、ラベルに含まれる会話を取得し、その会話のメールを取り出します。

そのため、過去のやり取りや自分が送った返信も、同じ会話に含まれていれば登録対象になります。「ラベルを付けた1通だけ」「相手から届いた依頼だけ」を厳密に抽出するコードではありません。まず会話の記録を一覧化し、必要な対応を人が選ぶための入門例です。

2.Gmailとスプレッドシートを準備する

Gmailに「task」ラベルを作る

Gmailで「task」という名前のラベルを作成し、テスト用の会話に付けてください。最初は、個人情報や機密情報を含まない、少数の会話だけで試します。

動作を確認できたら、Gmailのフィルタで送信者や件名などを条件にラベルを付ける方法も検討できます。ただし、検索条件だけで対応の必要性を完全に判断できるとは限らないため、対象が適切かを定期的に確認しましょう。

専用のスプレッドシートを作る

新しいスプレッドシートを作成し、シート名を「メール一覧」に変更します。1行目には、次の見出しをA列から順番に入力してください。

列見出し用途
A列メッセージID登録済みかを確認する識別情報
B列メール日時メールの日時
C列送信者送信者名やメールアドレス
D列件名メールの件名
E列会話URLGmailの会話を開くリンク
F列対応状況要確認・対応中・完了・対応不要など

A列は重複確認に使うため、内容を編集しないでください。並べ替えるときは、A〜F列をまとめて操作し、IDとメール情報の組み合わせを崩さないようにします。

また、スプレッドシートのURLにある「/d/」と「/edit」の間がスプレッドシートIDです。後ほど、コードの「YOUR_SPREADSHEET_ID」をこの値に置き換えます。

3.GASに取り込み用コードを設定する

スプレッドシートの「拡張機能」から「Apps Script」を開きます。新規プロジェクトの初期コードを、以下のコードに置き換えて保存してください。すでに別の処理がある場合は、既存コードを消さず、検証用のシートとプロジェクトを用意しましょう。

この例は、対象ラベルの会話が100件以内の小規模な運用を想定しています。100件を超える場合は、先頭だけを黙って処理するのではなく、エラーで停止するようにしています。

コードは実際のGoogleアカウントでは未実行です。まずは少数のテストメールで、後述の確認項目を確かめてください。

function importTaskMails() { const spreadsheetId = ‘YOUR_SPREADSHEET_ID’; const sheetName = ‘メール一覧’; const labelName = ‘task’; const maxThreads = 100; const headers = [ ‘メッセージID’, ‘メール日時’, ‘送信者’, ‘件名’, ‘会話URL’, ‘対応状況’ ]; const lock = LockService.getScriptLock(); if (!lock.tryLock(10000)) { throw new Error(‘別の取り込み処理が実行中です。’); } try { const book = SpreadsheetApp.openById(spreadsheetId); const sheet = book.getSheetByName(sheetName); if (!sheet) { throw new Error(‘「メール一覧」シートを作成してください。’); } const actualHeaders = sheet.getRange(1, 1, 1, 6).getDisplayValues()[0]; if (actualHeaders.join(‘|’) !== headers.join(‘|’)) { throw new Error(‘A1〜F1の見出しを確認してください。’); } const label = GmailApp.getUserLabelByName(labelName); if (!label) { throw new Error(‘Gmailに「task」ラベルを作成してください。’); } const threads = label.getThreads(0, maxThreads + 1); if (threads.length > maxThreads) { throw new Error(‘対象が100会話を超えています。対象範囲を見直してください。’); } const lastRow = sheet.getLastRow(); const existingIds = new Set( lastRow > 1 ? sheet.getRange(2, 1, lastRow – 1, 1) .getDisplayValues().map(row => row[0]) : [] ); const rows = []; const timeZone = book.getSpreadsheetTimeZone(); for (const thread of threads) { const url = thread.getPermalink(); for (const message of thread.getMessages()) { if (message.isDraft()) continue; const id = message.getId(); if (existingIds.has(id)) continue; rows.push([ id, Utilities.formatDate( message.getDate(), timeZone, ‘yyyy-MM-dd HH:mm:ss’ ), message.getFrom(), message.getSubject(), url, ‘要確認’ ]); existingIds.add(id); } } if (rows.length === 0) { console.log(‘追加対象はありません。’); return; } const requiredLastRow = lastRow + rows.length; if (requiredLastRow > sheet.getMaxRows()) { sheet.insertRowsAfter( sheet.getMaxRows(), requiredLastRow – sheet.getMaxRows() ); } const richRows = rows.map(row => row.map((value, column) => { const builder = SpreadsheetApp.newRichTextValue() .setText(String(value)); if (column === 4) builder.setLinkUrl(String(value)); return builder.build(); }) ); sheet.getRange(lastRow + 1, 1, rows.length, 6) .setRichTextValues(richRows); SpreadsheetApp.flush(); console.log(‘追加件数: ‘ + rows.length); } finally { lock.releaseLock(); } }

コードで変更する箇所

「YOUR_SPREADSHEET_ID」を、作成したスプレッドシートのIDに置き換えてください。シート名とラベル名を変更する場合は、実際の名前とコード内の指定を一致させます。

このコードでは、書き込み先をIDとシート名で指定しています。実行時に選択しているシートへ書く方法と異なり、意図しないタブへ追加することを避けやすい構成です。

数式として扱わせないため、文字列で書き込む

件名や送信者名は、メールの相手が指定できる情報です。この例では、RichTextValueを使って文字列として書き込み、件名の先頭に「=」があっても、数式として実行させない構成にしています。

日時も、この例ではスプレッドシートのタイムゾーンに合わせた文字列です。日付計算に使いたい場合は、日時を日付型で保存する設計へ変更し、その際も件名などの文字列とは扱いを分けてください。

4.重複登録を防ぐ仕組みと注意点

未読状態を「未登録」の代わりにしない

未読メールだけを取り込み、取り込み後に既読にする方法では、先に人が読んだメールが対象から外れてしまいます。また、一覧への登録と、メールを読んだことが同じ状態で管理されるため、確認漏れを判断しにくくなります。

今回のコードでは、メールのメッセージIDをA列に保存し、再実行時に照合します。これにより、メールの既読・未読と、一覧への登録状況を分けて扱えます。

完了した行は、削除せず残す

登録済みの行やメッセージIDを削除すると、そのメールが再び取り込まれる可能性があります。対応が終わったらF列を「完了」にし、フィルタで非表示にするなど、IDを残す運用にしてください。

また、同じ会話への新しい返信は別のメッセージIDを持つため、新しい行として追加されます。既存行の対応状況は変更しないので、前の依頼が完了していても、新しい返信の内容は別途確認します。

同時実行の対策にも範囲がある

LockServiceを使い、同じスクリプト内で取り込みが重なった場合に、処理を同時に進めないようにしています。ただし、別のスクリプトからの書き込みや、人が行う行削除・並べ替えまで止めるものではありません。

この入門例では、一つのアカウント・一つのプロジェクトから実行し、取り込み中の表の編集を避けてください。複数人での常時編集や大量取り込みに広げる場合は、保存先と重複管理の設計を見直します。

💡 あわせて読みたい関連記事

AIにGASの変更を依頼する場合も、動いたかどうかに加えて、取り込み条件や再実行時の動作を確認しましょう。コードを確認する視点をこちらで整理しています。

AI生成コードのレビュー方法|仕様・テスト・保守性を確認する実践手順 記事を読む →

5.初回は手動で実行し、動作を確認する

Apps Scriptで「importTaskMails」を選択して実行します。初回は、Googleサービスへのアクセス許可を求められることがあります。利用するアカウント、スクリプトの内容、要求される権限を確認してください。

GmailAppは、今回のコードで送信や削除を行わなくても、広いGmail権限を要求する場合があります。警告を機械的に無視して進めず、自分で管理しているコードか、権限の理由を理解できるかを確かめましょう。組織のアカウントでは、管理者の設定により実行できないこともあります。

確認する場面期待する動作
ラベル付きのテスト会話を初めて取り込む下書きを除く各メールが追加される
同じ状態でもう一度実行する登録済みメールが増えない
対象に既読メールがある未登録であれば追加される
対象に未読メールがある取り込みによって既読にならない
F列を「完了」にして再実行する既存行の「完了」が維持される
ラベル付きの会話に返信が増える新しいメールが別の行として追加される
件名が「=」から始まる数式として計算されず、文字列として表示される
ラベルや見出しを誤って指定するエラーで停止し、実行履歴から原因を確認できる

テスト中にGmailで会話を開くと、その操作によって既読になる場合があります。未読状態の確認では、スクリプトの動作と、自分の画面操作を分けて確認してください。

6.確認後に時間主導型トリガーを設定する

手動実行で問題がないことを確認したら、Apps Scriptの「トリガー」から定期実行を設定します。実行する関数は「importTaskMails」、イベントのソースは「時間主導型」を選びます。

最初は1時間ごとなど、必要な確認頻度に合わせた間隔で試してください。これはメール受信と同時に動く仕組みではなく、定期的にGmailを確認する方式です。正確な時刻での即時通知を保証するものでもありません。

インストール型トリガーは、作成したアカウントの権限で動きます。シートを共有しただけで、共有相手のGmailも自動的に取り込まれるわけではありません。同じ処理のトリガーを複数作らないよう、一覧も確認しましょう。

自動化を止める場合は、該当するトリガーを削除します。シートを閉じるだけでは定期実行は止まりません。担当者や利用アカウントが変わる場合にも、トリガーとアクセス権限を見直してください。

7.取り込まれないときの確認方法

結果が増えていないときは、対象がないのか、すでに登録済みなのか、エラーで停止したのかを分けて確認します。Apps Scriptの実行履歴から、実行日時・成否・エラー内容を確かめてください。

状況確認すること
「追加対象はありません」と表示されるラベル付き会話の有無、A列の登録済みID
シートが見つからないスプレッドシートIDと「メール一覧」の名前
ラベルが見つからない実行アカウントと「task」の名前
対象が100会話を超える不要な対象を整理するか、分割取り込みを設計する
時間や利用上限に関するエラー対象メール数、会話の長さ、実行間隔、公式クォータ
手動では動くが自動では動かないトリガーの関数名、作成アカウント、権限、実行履歴

100会話以内でも、一つの会話に大量のメールがあれば処理は重くなります。100という値は、この例で設けた確認用の上限であり、Googleが動作を保証する件数ではありません。

大量のメールを扱う場合は、対象を分割し、途中で停止しても再開できる仕組みが必要です。単に上限を大きくするだけでなく、実行時間や処理済み情報の管理も見直しましょう。

💡 あわせて読みたい関連記事

エラーが出たときは、メッセージと実行条件を整理すると調査を進めやすくなります。ログを読む手順と、相談時に情報を共有する注意点はこちらをご覧ください。

エラーログの読み方|原因を絞り込む調査手順と、安全な記録・共有の方法 記事を読む →

8.共有と運用で気をつけること

メールの情報をコピーすると、閲覧できる範囲が変わる

メール本文を保存しなくても、件名や送信者には個人情報や業務上の情報が含まれる場合があります。スプレッドシートへ転記すると、そのシートを閲覧できる人にも情報が見えるため、共有範囲を確認してください。

Gmailへのリンクを共有しても、それだけで元のメールを読む権限が付くわけではありません。一方、シートへコピーした件名や送信者は閲覧できるので、リンクの権限と転記した情報の公開範囲を分けて考えましょう。

スクリプトの編集権限も確認する

スプレッドシートに紐づいたスクリプトでは、シートの編集者がコードも編集できることがあります。トリガーは作成者の権限で実行されるため、コードを変更できる人の範囲にも注意が必要です。

まずは個人用の非公開シートで試し、共有が必要になった段階で、閲覧権限・編集権限・自動実行用プロジェクトの管理方法を整理してください。

9.よくある質問

個人のGoogleアカウントでも使えますか?

GmailとApps Scriptを利用でき、必要な権限を許可できる環境であれば試せます。ただし、個人アカウントとGoogle Workspaceでは利用上限などが異なり、組織の管理設定にも左右されます。利用環境での確認が必要です。

本文から担当者や期限を自動で決められますか?

今回のコードは、本文の解析や期限の抽出を行いません。まずは一覧を見て人が判断し、必要なら担当者・期限・メモの列をG列以降に追加してください。自動判定を追加する場合は、曖昧な表現や期限変更をどう扱うかも決める必要があります。

完了した会話のラベルは外してもよいですか?

今後の取り込み対象から外してよければ、手動で外す運用ができます。シートの既存行は残りますが、その会話が対象ラベルに含まれなくなると、以後の返信もこの処理では取り込めません。返信を継続監視する必要があるかで判断してください。

メールを1通ずつ選んで取り込めますか?

今回の例は、ラベルから会話を取得する方式です。メール単位のラベルや条件を厳密に扱いたい場合は、Gmail APIなどを使ったメッセージ単位の取得を検討します。会話の検索結果から全メールを取り出す処理と、個別メールの条件判定を混同しないことが大切です。

10.まずは少数のメールで、再実行まで確かめる

Gmailとスプレッドシートの連携では、最初に情報が表示されることに加えて、再実行しても重複しないか、返信が増えたときにどう動くかを確認することが重要です。未読状態と登録状況を分けることで、普段のメール確認と両立しやすくなります。

まずはテスト用の会話で手動実行し、登録内容・重複確認・対応状況の保持を確かめましょう。そのうえで定期実行を設定し、実行履歴と一覧を確認する運用を続けてください。

参考資料

Learning Tools

記事を検索したい方はここから!

辞書から探す

本文中で気になった概念やキーワードを、辞書ページで一覧から確認できます。

辞書を見る