GASでGmail添付ファイルをGoogleドライブへ自動保存|コード・PDF・CSV対応

【脱・手作業】GASでGmailの添付ファイルを保存する仕組みと自動化の罠

SkillStack Lab(スキスタ)運営者の「スタック」です。

Gmailに届く請求書やCSVなどの添付ファイルを、毎回ダウンロードしてGoogleドライブへ保存していませんか。

GAS(Google Apps Script)を使えば、条件に合うGmailを検索し、添付ファイルだけを指定したGoogleドライブへ自動保存できます。

この記事では、Gmailの添付ファイルをGoogleドライブへ保存する実用的なGASコードから、PDF・CSVだけに絞る方法、二重保存の防止、トリガー設定、6分制限への対策までまとめて解説します。

私はこれまで、GASを使った社内備品・PCの貸出管理や期限超過メール、簡易ワークフローの通知などを実務で構築・運用してきました。

その経験から強く感じるのは、業務自動化では「とりあえず動くコード」よりも、二重処理やエラーが起きても自分で原因を追える仕組みにすることが重要だという点です。

まずは少量のテストメールで動作を確認しながら、自社の運用に合わせて調整してください。

※本記事には広告・プロモーションが含まれています。

Gmailの添付ファイルを手作業で保存する状態とGASで自動保存する状態を比較した図解
この記事で分かること
  • Gmailの添付ファイルをGoogleドライブへ保存するGASコード
  • 件名・送信者・PDF・CSVで対象を絞る方法
  • 署名画像など不要なインライン画像を除外する方法
  • 保存済みメールを二重処理しない仕組み
  • 時間主導型トリガーで自動実行する方法
  • 6分制限・クォータ・権限・共有ドライブの注意点
目次

GASでGmailの添付ファイルをGoogleドライブへ保存するコード

最初に、今回作る仕組みの全体像を確認しておきましょう。

  1. Gmailから条件に一致するメールを検索する
  2. メールから添付ファイルを取得する
  3. PDFやCSVなど必要なファイルだけに絞る
  4. Googleドライブの指定フォルダへ保存する
  5. 処理済みのスレッドへラベルを付ける
GmailからGASで添付ファイルを取得しGoogleドライブへ保存する処理の流れ

1.保存先のGoogleドライブでフォルダIDを確認する

最初に、添付ファイルを保存したいGoogleドライブのフォルダを開きます。

ブラウザのURLが次のようになっている場合、folders/より後ろの部分がフォルダIDです。

https://drive.google.com/drive/folders/xxxxxxxxxxxxxxxxxxxx

この例では、xxxxxxxxxxxxxxxxxxxxの部分を後ほどGASへ設定します。

フォルダIDは保存先を特定する重要な情報です。コードを社外へ公開する場合は、実際に使用しているIDを載せないでください。

2.Apps Scriptへコードを貼り付ける

Apps Scriptで新しいプロジェクトを作成し、次のコードを貼り付けます。

以下は、件名に「請求書」を含むメールからPDF・CSVだけを保存する基本例です。

const CONFIG = {
  folderId: 'YOUR_FOLDER_ID',
  subjectKeyword: '請求書',
  senderContains: '',
  allowedExtensions: ['pdf', 'csv'],
  processedLabel: 'gas-saved',
  lookbackDays: 30,
  maxThreads: 50
};

function saveGmailAttachments() {
  const folder = DriveApp.getFolderById(CONFIG.folderId);

  const processedLabel =
    GmailApp.getUserLabelByName(CONFIG.processedLabel) ||
    GmailApp.createLabel(CONFIG.processedLabel);

  const query = buildSearchQuery_();

  const threads = GmailApp.search(
    query,
    0,
    CONFIG.maxThreads
  );

  const cutoff =
    Date.now() - CONFIG.lookbackDays * 24 * 60 * 60 * 1000;

  for (const thread of threads) {
    try {
      let matchedMessageCount = 0;
      let savedFileCount = 0;

      const messages = thread.getMessages();

      for (const message of messages) {

        if (message.getDate().getTime() < cutoff) {
          continue;
        }

        if (
          CONFIG.subjectKeyword !== '' &&
          !message.getSubject().includes(CONFIG.subjectKeyword)
        ) {
          continue;
        }

        if (
          CONFIG.senderContains !== '' &&
          !message.getFrom()
            .toLowerCase()
            .includes(CONFIG.senderContains.toLowerCase())
        ) {
          continue;
        }

        matchedMessageCount++;

        const attachments = message.getAttachments({
          includeInlineImages: false,
          includeAttachments: true
        });

        for (const attachment of attachments) {
          const fileName = attachment.getName();

          if (!isAllowedExtension_(fileName)) {
            continue;
          }

          const receivedAt = Utilities.formatDate(
            message.getDate(),
            Session.getScriptTimeZone(),
            'yyyyMMdd_HHmmss'
          );

          const outputName =
            receivedAt + '_' + fileName;

          folder
            .createFile(attachment)
            .setName(outputName);

          savedFileCount++;
        }
      }

      if (matchedMessageCount > 0) {
        thread.addLabel(processedLabel);
      }

      console.log(
        '保存完了: ' +
        savedFileCount +
        '件 / thread=' +
        thread.getId()
      );

    } catch (error) {
      console.error(
        '処理失敗: thread=' +
        thread.getId() +
        ' / ' +
        error.message
      );
    }
  }
}

function buildSearchQuery_() {
  const conditions = [
    'has:attachment',
    'newer_than:' + CONFIG.lookbackDays + 'd',
    '-label:' + CONFIG.processedLabel
  ];

  if (CONFIG.subjectKeyword !== '') {
    conditions.push(
      'subject:"' + CONFIG.subjectKeyword + '"'
    );
  }

  if (CONFIG.senderContains !== '') {
    conditions.push(
      'from:' + CONFIG.senderContains
    );
  }

  return conditions.join(' ');
}

function isAllowedExtension_(fileName) {
  if (CONFIG.allowedExtensions.length === 0) {
    return true;
  }

  const parts =
    fileName.toLowerCase().split('.');

  if (parts.length === 1) {
    return false;
  }

  const extension = parts.pop();

  return CONFIG.allowedExtensions.includes(extension);
}

最初に変更する場所は、基本的にCONFIGの中だけです。

設定 意味 設定例
folderId 保存先フォルダ GoogleドライブのフォルダID
subjectKeyword 件名に含む文字 請求書
senderContains 送信元 billing@example.com
allowedExtensions 保存する拡張子 pdf、csv
lookbackDays 何日前まで検索するか 30
maxThreads 1回で処理する最大スレッド数 50

PDFだけなら、次のように変更します。

allowedExtensions: ['pdf']

すべての通常添付ファイルを対象にしたい場合は空配列にします。

allowedExtensions: []

送信者を限定しないなら、senderContainsは空文字のままで構いません。

3.最初は手動実行して権限を承認する

コードを保存したら、まずsaveGmailAttachmentsを手動で実行します。

このスクリプトはGmailのメッセージとGoogleドライブへアクセスするため、初回実行時にはGoogleアカウントによる権限の承認が必要です。

いきなり数百件を処理せず、検索条件を狭くして5〜10件程度のテストメールから確認するのがおすすめです。

保存先、ファイル名、対象メール、除外したい添付ファイルが想定どおりになっていることを確認してから自動実行へ進みます。

Gmailの検索条件で対象メールを絞り込む

Gmail添付保存の自動化では、コードを書くこと以上に「どのメールを対象にするか」が重要です。

受信メールを無条件ですべて処理すると、必要のない添付ファイルまで保存する原因になります。

Gmailから不要な画像まで保存してしまう問題と添付ファイルを絞り込む必要性を説明した図解

GASのGmailApp.search()では、普段Gmailの検索欄で使っている検索演算子を利用できます。

検索条件 意味
has:attachment 添付ファイルがある has:attachment
subject: 件名を指定 subject:”請求書”
from: 送信者を指定 from:billing@example.com
filename: ファイル名・種類を指定 filename:pdf
newer_than: 一定期間内のメール newer_than:30d
-label: 指定ラベルを除外 -label:gas-saved

Google公式のGmailヘルプでも、has:attachmentfilename:pdfなどの検索演算子が案内されています。

Gmail公式:検索条件を絞り込む方法

from:による絞り込みは対象メールを選ぶための条件です。これだけで送信者の真正性や安全性を保証できるわけではありません。

署名ロゴなどのインライン画像は除外する

メールには、本文内の署名ロゴなどが画像データとして含まれる場合があります。

Google Apps ScriptのgetAttachments()には、通常の添付ファイルとインライン画像を分けて取得するオプションがあります。

今回のコードでは次の指定を入れています。

message.getAttachments({
  includeInlineImages: false,
  includeAttachments: true
});

これにより、本文内のインライン画像を除外し、通常の添付ファイルを取得できます。

さらに拡張子をPDF・CSVだけに絞ることで、保存対象をより限定しています。

Googleドライブの保存先と共有ドライブの注意点

マイドライブ内のフォルダを扱う基本的な処理では、DriveApp.getFolderById()でフォルダを取得し、createFile()で添付ファイルを保存できます。

ただし、会社でGoogle Workspaceを使用している場合は「共有ドライブ」へ保存したいケースもあるでしょう。

共有ドライブを本格的に扱う場合は、単純に「DriveAppならすべて対応できる」と考えないでください。Googleは、共有ドライブへのアクセスや最新機能の利用にはAdvanced Drive Serviceの使用を案内しています。

共有ドライブを業務で使用する場合は、保存先の権限、スクリプト実行アカウント、共有ドライブ側のポリシーを確認したうえで設計してください。

Google公式の現在の仕様は、Apps ScriptのDriveAppリファレンスで確認できます。

トリガーを設定してGmail添付保存を自動実行する

手動実行で問題なく保存できたら、Apps Scriptの「時間主導型トリガー」を設定します。

Apps Scriptの画面左側から「トリガー」を開き、「トリガーを追加」を選択します。

  1. 実行する関数:saveGmailAttachments
  2. イベントのソース:時間主導型
  3. 時間ベースのタイプ:分・時間・日単位から選択
  4. 実行頻度を選んで保存

たとえば「15分おき」「30分おき」「1時間おき」など、業務上必要な更新頻度に合わせます。

Google Apps Scriptでは、時間主導型トリガーを最短1分間隔まで設定できます。ただし、一般的なバックオフィス業務なら常に最短間隔へする必要はありません。

GASを自動実行する前に処理頻度や重複処理を確認する重要性を示した図解

インストール型トリガーは、トリガーを作成したGoogleアカウントの権限で動作します。

そのため、個人担当者のアカウントだけに依存してしまうと、異動・退職時の引き継ぎが問題になる可能性があります。

スタック

自動化そのものより、「誰のアカウントで動き、担当者が変わったとき誰が保守するか」まで決めておくことが実務では重要です。

処理済みラベルで二重保存を防ぐ

トリガーを繰り返し実行するときに必須なのが、同じ添付ファイルを何度も保存しない仕組みです。

今回のコードでは、処理後のGmailスレッドへgas-savedというラベルを付けています。

検索条件には、

-label:gas-saved

を含めているため、通常の再実行では同じスレッドを対象外にできます。

この方法は「処理済みラベルをスレッド単位で付ける」基本形です。同じメールスレッドへ後日新しい添付ファイルが追加される運用では、新着添付まで除外する可能性があります。そのような業務では、Gmail APIやメッセージIDを使った処理履歴管理を検討してください。

GASには1回6分の実行時間制限がある

小規模な処理では問題になりにくいものの、月末などに大量のメールをまとめて処理する場合はApps Scriptのクォータを意識する必要があります。

GASの1回6分という実行時間制限と大量処理で途中終了するリスクを説明した図解

2026年8月18日時点のGoogle公式情報では、通常のApps Scriptは個人アカウント・Google Workspaceともに1実行あたり6分です。

主な制限 個人アカウント Google Workspace
1回のスクリプト実行 6分 6分
Gmail read/write 20,000回/日 50,000回/日
トリガー総実行時間 90分/日 6時間/日

クォータは変更される可能性があるため、大量処理へ使う場合はGoogle公式のApps Scriptクォータを確認してください。

今回のコードでは、1回に検索するスレッド数をmaxThreads: 50にしています。

処理件数が多い場合は、1回にすべて処理しようとするよりも、検索条件と処理件数を分割する方が管理しやすくなります。

GAS自動化で注意したい6分制限、二重処理、不要ファイル保存への対策をまとめた図解

GAS自動化全般で起こりやすい制限や属人化については、GASでスプレッドシートを自動化するときの6分制限と運用上の注意点でも詳しく整理しています。

Gmail添付保存で起こりやすいエラーと対策

コードを業務で使い始めると、「保存されない」「同じファイルが増える」などの問題が起こることがあります。

代表的な症状と確認ポイントを整理します。

症状 主な確認ポイント
対象メールが見つからない 件名・送信元・期間の検索条件を確認
署名ロゴまで保存される includeInlineImages: falseを確認
PDF以外も保存される allowedExtensionsを確認
同じファイルが増える 処理済みラベルと検索条件を確認
フォルダが見つからない フォルダIDと実行アカウントの権限を確認
途中で処理が止まる 6分制限・クォータ・処理件数を確認
自動実行だけ失敗する トリガーを作成したアカウントと実行履歴を確認

自動実行に失敗したら「実行数」を確認する

時間主導型トリガーは、人が画面を見ていない状態で動きます。

処理に失敗した場合はApps Script左側の「実行数」を開き、ステータスとエラーメッセージを確認してください。

インストール型トリガーで例外が発生した場合、Googleから失敗通知メールが送られる仕組みもあります。

自動化した後も、ときどき実行履歴を確認し、「動いているつもり」の状態を作らないことが重要です。

CSVの文字化けは「保存」と「読み込み」を分けて考える

GASでCSVを扱うと「文字化け」がよく話題になりますが、単純にメール添付のBlobをGoogleドライブへ保存するだけなら、GAS側でCSV本文を文字列へ変換する必要はありません。

今回のコードも、添付ファイルをそのままDriveへ保存しています。

文字コードを意識する必要が大きくなるのは、CSVをgetDataAsString()などで読み込み、文字列として加工・再出力するときです。

「保存したCSVをExcelで開いたら文字化けした」という場合は、元ファイルの文字コードや開く側のアプリも確認してください。GASで保存したことだけが原因とは限りません。

実務でGASを使うならセキュリティと属人化にも注意する

Gmailの添付ファイルを自動保存できると便利ですが、メールやGoogleドライブには会社の重要情報が含まれる可能性があります。

Google Apps ScriptでGmailの添付ファイルへアクセスする処理には、Gmailへのアクセス権限が必要です。

そのため、コードだけでなく次の点まで確認してください。

  • どのGoogleアカウントでスクリプトを動かすか
  • 保存先フォルダを誰が閲覧できるか
  • 対象とする送信者・件名をどこまで限定するか
  • 担当者の異動・退職時に誰が引き継ぐか
  • エラー発生時に誰が実行履歴を確認するか

特に経理関係の請求書や個人情報を含む添付ファイルを扱う場合は、保存先の共有権限を広げすぎないよう注意しましょう。

私自身、GASで通知や簡易ワークフローを運用してきましたが、「作った本人しか修正できない状態」は避けるようにしています。便利な自動化でも、担当者が休んだだけで業務が止まるなら、会社としては別のリスクを作っているからです。

スタック

業務で使うなら「コードが動いた」で完成ではありません。エラー時の確認方法と引き継ぎ方法まで決めて、初めて運用できる仕組みになります。

コピペだけで本番運用せずコードの意味も理解する

GASのサンプルコードは便利ですが、インターネット上のコードをそのまま会社のGmailへ接続することはおすすめしません。

最低限、次の部分が何をしているのか確認してください。

  • GmailApp.search()で何を検索しているか
  • getAttachments()で何を取得しているか
  • DriveAppでどこへ保存しているか
  • どの時点で処理済み扱いになるか
  • エラーが発生したとき何が残るか

変数、配列、条件分岐、ループ、関数といったJavaScriptの基本を理解していれば、自社の件名や保存ルールが変わっても修正しやすくなります。

「GASをどこまで自作してよいのか」「6分制限や属人化まで含めて考えたい」という方は、先ほど紹介したGAS自動化の注意点も参考にしてください。

GASでGmail添付ファイルを保存するときのよくある質問

GASでGmailの添付ファイルを自動保存できますか?

はい。GmailAppで対象メールを検索し、getAttachments()で添付ファイルを取得して、DriveAppからGoogleドライブへ保存できます。時間主導型トリガーを設定すれば定期的な自動実行も可能です。

PDFだけをGoogleドライブへ保存できますか?

できます。この記事のコードならallowedExtensions: ['pdf']へ変更してください。Gmail検索のfilename:pdfを組み合わせて候補メール自体を絞る方法もあります。

特定の送信者から届いた添付だけ保存できますか?

できます。この記事のsenderContainsへ対象のメールアドレスを設定すると、検索条件とメッセージ側の両方で送信者を確認します。

メール本文のロゴ画像まで保存されるのを防げますか?

getAttachments()のオプションでincludeInlineImages: falseを指定すれば、通常の添付ファイルを取得しつつインライン画像を除外できます。

共有ドライブにも自動保存できますか?

共有ドライブを扱う場合は権限やAPIの対応範囲を確認する必要があります。Googleは共有ドライブへのアクセスや最新機能についてAdvanced Drive Serviceの利用を案内しています。

GASは大量の添付ファイルも一度に保存できますか?

件数によります。Apps Scriptには1回6分の実行時間制限やGmail read/writeなどのクォータがあります。大量処理では検索条件を絞り、1回の処理件数を分割してください。

まとめ|Gmail添付保存は小さくテストしてから自動化する

GASを使えば、Gmailの添付ファイルをGoogleドライブへ自動保存できます。

基本的な流れはシンプルです。

  • Gmailの検索条件を決める
  • 添付ファイルを取得する
  • PDFやCSVなど必要な形式だけに絞る
  • Googleドライブへ保存する
  • 処理済みを記録して二重保存を防ぐ
  • トリガーで定期実行する
手作業と不安定な自動化、運用まで考えた自動化を比較した図解

ただし、会社の業務で使うなら「自動で保存できた」で終わりではありません。

6分制限、二重保存、権限、エラー確認、担当者の引き継ぎまで含めて設計することで、長く使える自動化になります。

最初は少量のテストメールで動作を確認し、問題がなければ対象メールと実行頻度を徐々に広げていくのが安全です。

この記事のコードを読んでも「どこを変更すればよいか分からない」という場合は、いきなり本番運用せず、JavaScriptやGASの基礎から確認する方法もあります。

Udemyを使う場合は、現在公開されているGAS・JavaScript関連講座の内容、更新日、プレビューなどを比較し、自分が必要な部分を学べるか確認してください。

GASを自力で修正できるようにしたい方へ


次は、仕事で使えるスキルをもう一段積み上げる

SkillStack Labでは、Excel・生成AI・自動化・オンライン学習を、実務で使うことを前提に解説しています。今の課題に近いテーマから次の記事へ進んでみてください。

生成AIを仕事で使いたい

ChatGPTなどを「触ったことがある」から、実際の仕事で使えるレベルへ進みたい方へ。

生成AIの勉強方法・学習順序を見る →

Excel作業を自動化したい

関数だけでは限界を感じてきたら、Power Query・VBA・Pythonなども含めて自動化を考えます。

Excel業務を自動化する方法を見る →

体系的にスキルを学びたい

Excel・VBA・Python・生成AIなどを、動画講座で効率よく学びたい方へ。

社会人向けUdemy講座の選び方を見る →

何から始めるか迷ったら、今の仕事で最も時間がかかっている作業や、身につけたいスキルに近いテーマから選んでください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次