Gmail下書きの添付忘れを送信前に発見|GASで件数と容量をdry-run監査

Gmail下書きの添付忘れを送信前に発見|GASで件数と容量をdry-run監査

ツールおじ|GoogleアカウントをAIエージェント化

ツールおじ|GoogleアカウントをAIエージェント化

「資料を添付します」と書いたのに、ファイルを付けないまま送信しかけた。反対に、大きな添付が複数入り、送信直前に容量へ気づいた。Gmail下書きが増えるほど、この確認は目視だけでは抜けやすくなります。

この記事では、Gmailの下書きを送信・削除・変更せずに読み取り、本文や件名に「添付」「ファイル」「attachment」があるのに添付数が0件の候補と、容量上限を超える候補だけを集計する無料GASを紹介します。宛先、件名、本文、ファイル名はログへ出しません。

先に知っておきたいこと

  • できること: Gmail下書きの添付数、合計容量、添付を示す語と実ファイルの不一致候補をdry-runで確認できます。
  • しないこと: メール送信、下書き削除、本文変更、添付追加、Drive保存、ラベル変更は行いません。
  • 必要な権限: 初回実行時にGmail下書きの読み取りに必要な権限確認が表示されます。内容に納得できない場合は許可せず停止してください。
  • 守る範囲: 最大50件までを既定値にし、実際の宛先・件名・本文・ファイル名をログへ残しません。
  • 判定の位置づけ: 「添付が必要か」を確定するものではなく、人が送信前に開いて確認する候補を絞るための監査です。

この方法が向いている場面

  • 見積書、申込書、案内資料などをGmail下書きで準備している。
  • 定型文に「添付」「ファイル」を含めることが多い。
  • 自動送信は避け、最後は人がGmail画面で確認したい。
  • 大きな添付が混ざっていないか、送信前に件数だけ先に把握したい。

添付を自動でDriveへ保存する処理や、自動送信をしたい場合のコードではありません。まず読み取り専用の監査を分離しておくと、誤送信や意図しない保存を避けやすくなります。

コピーして使える無料GAS

function auditGmailDraftAttachmentsFree() {
  const config = {
    dryRun: true,
    maxDrafts: 50,
    maxTotalAttachmentBytes: 8 * 1024 * 1024,
    attachmentWords: /添付|ファイル|attachment/i,
    maxIssueRowsInLog: 20
  };

  validateAttachmentAuditConfig_(config);

  // 安全装置: この無料コードは読み取り専用のdry-runだけを許可します。
  if (config.dryRun !== true) {
    throw new Error('dryRun は true のまま実行してください。');
  }

  const allDrafts = GmailApp.getDrafts();
  const targetDrafts = allDrafts.slice(0, config.maxDrafts);
  const issues = [];
  let attachmentCount = 0;
  let attachmentBytes = 0;
  let keywordWithoutAttachmentCount = 0;
  let overSizeCount = 0;

  targetDrafts.forEach(function(draft, index) {
    const message = draft.getMessage();
    const subject = String(message.getSubject() || '');
    const plainBody = String(message.getPlainBody() || '');
    const attachments = message.getAttachments({
      includeInlineImages: false,
      includeAttachments: true
    });

    const totalBytes = attachments.reduce(function(sum, attachment) {
      const size = Number(attachment.getSize() || 0);
      return sum + (Number.isFinite(size) ? size : 0);
    }, 0);

    const mentionsAttachment =
      config.attachmentWords.test(subject + '\n' + plainBody);
    const flags = [];

    if (mentionsAttachment && attachments.length === 0) {
      flags.push('ATTACHMENT_WORD_WITHOUT_FILE');
      keywordWithoutAttachmentCount += 1;
    }

    if (totalBytes > config.maxTotalAttachmentBytes) {
      flags.push('TOTAL_ATTACHMENT_SIZE_OVER_LIMIT');
      overSizeCount += 1;
    }

    if (attachments.some(function(attachment) {
      return Number(attachment.getSize() || 0) === 0;
    })) {
      flags.push('ZERO_BYTE_ATTACHMENT');
    }

    attachmentCount += attachments.length;
    attachmentBytes += totalBytes;

    if (flags.length > 0) {
      issues.push({
        draftNo: index + 1,
        flags: flags,
        attachmentCount: attachments.length,
        totalMiB: roundMiB_(totalBytes)
      });
    }
  });

  const summary = {
    dryRun: true,
    availableDrafts: allDrafts.length,
    checkedDrafts: targetDrafts.length,
    truncated: allDrafts.length > targetDrafts.length,
    attachmentCount: attachmentCount,
    totalAttachmentMiB: roundMiB_(attachmentBytes),
    keywordWithoutAttachmentCount: keywordWithoutAttachmentCount,
    overSizeCount: overSizeCount,
    issueCount: issues.length,
    issues: issues.slice(0, config.maxIssueRowsInLog)
  };

  // 宛先・件名・本文・ファイル名は出力しません。
  console.log(JSON.stringify(summary));
  return summary;
}

function validateAttachmentAuditConfig_(config) {
  if (!config || typeof config !== 'object') {
    throw new Error('設定が見つかりません。');
  }
  if (config.dryRun !== true) {
    throw new Error('dryRun=false はこの無料コードでは利用できません。');
  }
  if (!Number.isInteger(config.maxDrafts) ||
      config.maxDrafts < 1 ||
      config.maxDrafts > 100) {
    throw new Error('maxDrafts は1〜100の整数にしてください。');
  }
  if (!Number.isFinite(config.maxTotalAttachmentBytes) ||
      config.maxTotalAttachmentBytes < 1024 * 1024 ||
      config.maxTotalAttachmentBytes > 20 * 1024 * 1024) {
    throw new Error('容量上限は1MiB〜20MiBの範囲にしてください。');
  }
  if (!Number.isInteger(config.maxIssueRowsInLog) ||
      config.maxIssueRowsInLog < 1 ||
      config.maxIssueRowsInLog > 50) {
    throw new Error('maxIssueRowsInLog は1〜50の整数にしてください。');
  }
  if (!(config.attachmentWords instanceof RegExp)) {
    throw new Error('attachmentWords は正規表現で指定してください。');
  }
}

function roundMiB_(bytes) {
  return Math.round((bytes / 1024 / 1024) * 100) / 100;
}

実行手順

  1. Googleスプレッドシートを新規作成し、「拡張機能」からApps Scriptを開きます。
  2. 上のコードを貼り付けて保存します。
  3. 最初はテスト用のGmail下書きを2件だけ用意します。1件は「資料を添付します」と書いて添付なし、もう1件は小さなテストファイルを付けます。
  4. `auditGmailDraftAttachmentsFree` を選び、実行します。
  5. 権限確認を読み、対象アカウントがテスト用であることを確認してから許可します。
  6. Apps Scriptの「実行ログ」で、`checkedDrafts`、`issueCount`、`flags`を確認します。
  7. `draftNo`は監査時の並び順です。候補が出たらGmailの下書きを人が開き、添付の要否と容量を確認します。

最初から業務用下書き全件へ使わず、テスト下書きで「添付語あり・添付なし」と「添付あり」の両方を確認してください。

ログの読み方

  • `ATTACHMENT_WORD_WITHOUT_FILE`: 件名または本文に添付を示す語がある一方、通常添付が0件です。
  • `TOTAL_ATTACHMENT_SIZE_OVER_LIMIT`: その下書きの添付合計が設定値を超えています。
  • `ZERO_BYTE_ATTACHMENT`: サイズ0として取得された添付があります。取得仕様やファイル状態を人が確認してください。
  • `truncated: true`: 下書き総数が`maxDrafts`を超え、今回は先頭の一部だけを確認しています。

ログには本文やファイル名を出していません。候補の内容確認はGmail画面で行い、機密情報や個人情報をApps Scriptのログへ複製しない運用にしてください。

失敗しやすい点と制限

  • Gmailの署名内に「添付ファイル」という定型文があると、確認不要な候補が増える場合があります。正規表現の語を自分の運用に合わせて狭めてください。
  • Google Driveの共有リンクはGmailの添付ファイルとして数えません。リンク送付を確認したい場合は、別のURL監査として設計してください。
  • インライン画像は既定で除外しています。署名画像を通常添付として数えないためです。
  • Apps ScriptやGmailには利用上限があります。大量の下書きがある場合も`maxDrafts`を一度に増やさず、少量で確認してください。
  • 下書きの取得順は運用中に変わることがあります。`draftNo`を恒久IDとして保存しないでください。
  • 添付の内容、安全性、法令・契約上の送付可否は判定しません。送信前の最終判断は利用者が行ってください。
  • このコードは下書きを送信、削除、変更しません。自動送信や自動削除へ改変する場合は、別のレビューと停止手順が必要です。

公開前チェックリスト

  • [ ] `dryRun: true`のままになっている。
  • [ ] テスト用下書きで期待する2パターンを確認した。
  • [ ] `maxDrafts`を必要以上に増やしていない。
  • [ ] 宛先、件名、本文、ファイル名をログへ追加していない。
  • [ ] 候補をGmail画面で人が確認してから送信する。
  • [ ] 添付の内容や送付可否を自動判定だと思い込まない。

まとめ

添付忘れ対策で最初に自動化したいのは、送信そのものではなく「確認候補を安全に絞ること」です。この無料GASは、下書きを変更せず、添付を示す語と添付数、合計容量だけをdry-run監査します。

定型連絡を複数テンプレートから作り、送信前の人間確認を残したい場合は、次の有料Tipsが関連します。

Gmail下書きテンプレGAS Proを見る

GasおじのTips商品一覧を見る

※本記事は学習・検証用の例です。実データへ適用する前に、テスト用下書きと少量のデータで確認してください。Google公式・Google公認の商品ではありません。Google側の仕様、権限、利用上限、アカウント設定により動作条件が変わる場合があります。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事の販売者

ツールおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

この販売者が書いた他の記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • Gmailの自動ラベルが競合する前に確認|GASで仕分けルールをdry-run監査

関連のおすすめ記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    1 %獲得
    (5 円相当)
    SmartSenior

    SmartSenior

  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化