GASの時間主導トリガーが重なって同じメールが二重に送られる
先に結論
送信処理を LockService.getScriptLock() のロックで囲み、取れなかった回は何もせず終えます。
送る直前にその行へ「送信中」と書き、送り終えたら日時にします。
- 「トリガー」で同じ関数が2つ登録されていないか見る
- 2つ以上ある1つを残して削除する
- 1つだけ前の回と次の回が重なっている可能性
- 送信の関数をロック付きのコードに置き換える
- 「送信中」のまま残った行は自動では送り直さない
- 1行に1通だけ届けば解決
※2026年11月時点の公式ドキュメントに基づきます。コードは実機未実行で、模擬環境で確認済みです。

エラーも出ずに二重になる理由
前の回が「未通知」と読む次の回も「未通知」と読む
送信済みを書く前に次の回が読むと、両方が送ってしまう(この記事の推測)
同時実行数の上限は1ユーザー30です。前の回が長引くと次の回と並ぶ、というのはこの記事の推測です。
出典: Google サービスの割り当て
1トリガーの重複登録を確かめる
- 通知の一覧のシートで「拡張機能」 → 「Apps Script」
- 左の「トリガー」で、同じ関数名が2行以上ないか見る
- 関数名と間隔(5分おき等)を控え、1行だけ残して縦3点の「その他」 → 「トリガーを削除」
他人のトリガーは自分の画面に出ないため、共同編集者にも見てもらいます(前任者のトリガー)。
出典: コンテナにバインドされたスクリプト / トラブルシューティング / インストール可能なトリガー
2ロック付きのコードに置き換える
1行目が見出し、A〜C列が宛先・件名・本文、D列が「通知済み」(空欄か日時。チェックボックス不可)の一覧が前提です。A列が宛先でない表には使えません。
- 古い関数のトリガーを、関数名と間隔を控えて削除
- シートの「拡張機能」から開いたエディタで(単独のスクリプトは対象外)、今の送信用の関数を消し、下のコードを定数2行ごと関数の外に貼って保存
- 上部のプルダウンで notifyNew を選び「実行」。確認画面が出たら案内に従って許可
- 「トリガーを追加」で notifyNew を控えた間隔の時間主導型で作り、「保存」
実行するとD列が空の行には本当に送られます。 送信済みを別の方法で管理していたなら全員に再送になるので、先に送り終えた行のD列を埋めます。試すなら自分宛ての1行だけ空けます。今の関数の添付やCCは引き継がれません。
const SHEET_NAME = 'シート1'; // ← 自分のシート名に
const DONE_COL = 4; // ← 「通知済み」の列番号(D列なら4)
function notifyNew() {
const lock = LockService.getScriptLock();
if (!lock.tryLock(1000)) {
console.log('前の回が実行中のため終了');
return;
}
try {
const start = Date.now();
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const last = sheet.getLastRow();
if (last < 2) return;
const rows = sheet.getRange(2, 1, last - 1, DONE_COL).getValues();
let row = 1;
for (const r of rows) {
row++;
if (Date.now() - start > 4 * 60 * 1000) break;
const to = r.shift(), subject = r.shift(), body = r.shift();
if (!to || r.pop() !== '') continue;
const cell = sheet.getRange(row, DONE_COL);
cell.setValue('送信中');
SpreadsheetApp.flush();
MailApp.sendEmail(to, subject, body);
cell.setValue(new Date());
}
} finally {
SpreadsheetApp.flush();
lock.releaseLock();
}
}
待ち1秒と4分での区切りは、6分の実行時間の上限を避けるこの記事の提案です。
出典: 自動化のクイックスタート / インストール可能なトリガー / Class Range / Class Lock / Class LockService / Google サービスの割り当て
3「送信中」のまま残った行を片付ける
- エディタ左側の「実行」を開き、「フィルタを追加」 → 「ステータス」で「失敗」「タイムアウト」に絞る
- 失敗した回を開き、エラー文を見て原因を先に直す(この記事の提案)
- 止まった回の「送信中」の行の宛先に、届いたか聞く
- 「実行」に実行中の回が無いのを確かめ、届いていれば日時を手で入れ、なければセルを空にする(確認はこの記事の提案)
出典: ロギング
それでも二重に届く時のチェックリスト
出典: Class LockService / コンテナにバインドされたスクリプト