退勤の打刻を忘れても、本人が気づくのは翌日以降の勤怠集計で表示がおかしいことに気づいたときになりがちです。
Google Apps Scriptで打刻シートを毎晩自動でチェックし、当日中に本人へリマインドを送り、翌朝は管理者向けにまとめて知らせる仕組みを整理しています。
完成イメージと前提にする打刻シート
完成すると、勤務日ごとの打刻記録を夜間にスクリプトが走査し、退勤時刻が空欄のまま残っている本人だけへ自動でメールが届きます。
本人が対応を忘れたまま放置された場合に備えて、翌朝には管理者向けのサマリーメールも別途送られる二段構えです。
この記事のコードは、勤怠打刻の仕組みを自作する場合に一般的な列構成である「氏名・出勤時刻・退勤時刻・メールアドレス」の4列だけで完結させています。
すでに独自の打刻シートを運用している場合は、列の並びだけ自分の環境に合わせて読み替えてください。
出勤・退勤の各時刻は日付と時刻の両方を含む形で記録されている状態が前提で、別途「日付」列を用意する必要はない設計です。
打刻の仕組みそのものをまだ用意していない場合は、まず打刻機能の作り方を確認してから、この記事のリマインド機能を追加する順番をおすすめします。

手順
シートの準備から夜間トリガーの設定まで、3つのステップで完成する内容です。
Step1. 打刻シートの列構成を確認する
対象のスプレッドシートを開き、1行目に「氏名」「出勤時刻」「退勤時刻」「メールアドレス」の見出しが入っていることを確認します。
出勤時刻・退勤時刻の列には、打刻のたびにその日の日付と時刻を含むデータが書き込まれている必要があります。
続けてスプレッドシート上部のメニューから拡張機能を開き、Apps Scriptを選ぶとスクリプトエディタが立ち上がる流れです。
Step2. コードを貼り付ける(コード全文)
スクリプトエディタに表示されているコードを全て消し、次のコードを貼り付けます。
function notifyMissingClockOut() {
const sheetName = '打刻管理'; // ★ここを変える: 対象シート名
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
if (!sheet) {
throw new Error(`シート「${sheetName}」が見つかりません`);
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return;
}
const data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
const today = new Date();
data.forEach((row) => {
const [name, clockIn, clockOut, email] = row;
if (!(clockIn instanceof Date)) {
return;
}
if (clockOut) {
return;
}
const isToday = clockIn.getFullYear() === today.getFullYear()
&& clockIn.getMonth() === today.getMonth()
&& clockIn.getDate() === today.getDate();
if (!isToday) {
return;
}
const subject = '退勤打刻の確認';
const body = [
`${name}様`,
'',
'本日の退勤打刻が記録されていません。',
'すでに退勤済みの場合は、お手数ですが打刻をお願いします。',
].join('\n');
GmailApp.sendEmail(email, subject, body);
});
} data.forEach の中で行ごとに出勤時刻が今日の日付かどうかを判定し、今日分でかつ退勤時刻が空欄の行だけをリマインド対象として絞り込んでいます。
退勤時刻がすでに入力されている行や、出勤時刻が今日以外の行は return でスキップされるため、過去の打刻漏れに毎晩重複して通知が届くことはありません。
Step3. 夜間の時間トリガーを設定する
スクリプトエディタの左側にある時計マークの「トリガー」を開き、右下の「トリガーを追加」から設定します。
実行する関数にはnotifyMissingClockOutを選び、イベントのソースは時間主導型、種類は日付ベースのタイマーを選んで、21時から22時など終業後の時間帯を指定します。
保存すれば、その日の退勤打刻が漏れている人にだけ、終業後のタイミングで自動的にメールが届く仕組みが完成です。
打刻漏れの本人通知だけでなく、期限管理そのものを自動化したい場合はリマインダーメールの基本設計も参考にしてください。

翌朝の管理者向けサマリー
本人への当日リマインドだけでは、そのまま対応せず出社日を迎えるケースも残ります。
そこで前日分の打刻漏れをまとめて管理者へ知らせる関数を、本人向けの通知とは別に用意します。
function sendMissingClockOutSummary() {
const sheetName = '打刻管理'; // ★ここを変える: 対象シート名
const adminEmail = '[管理者のメールアドレス]'; // ★ここを変える: 通知先
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
if (!sheet) {
throw new Error(`シート「${sheetName}」が見つかりません`);
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return;
}
const data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const missingNames = [];
data.forEach((row) => {
const [name, clockIn, clockOut] = row;
if (!(clockIn instanceof Date)) {
return;
}
if (clockOut) {
return;
}
const isYesterday = clockIn.getFullYear() === yesterday.getFullYear()
&& clockIn.getMonth() === yesterday.getMonth()
&& clockIn.getDate() === yesterday.getDate();
if (!isYesterday) {
return;
}
missingNames.push(name);
});
if (missingNames.length === 0) {
return;
}
const subject = `前日の退勤打刻漏れサマリー(${missingNames.length}件)`;
const body = [
'前日、退勤打刻が記録されなかった従業員は以下のとおりです。',
'',
missingNames.join('\n'),
].join('\n');
GmailApp.sendEmail(adminEmail, subject, body);
} この関数は8時前後に実行するトリガーを別途追加し、対象を「今日」ではなく「前日」に変えているだけで、判定の骨格はStep2のコードと同じです。
missingNames が空の場合はメール自体を送らないため、打刻漏れがなかった日に管理者へ空の通知が届くこともありません。
本人向けの当日リマインドと、管理者向けの翌朝サマリーは目的も送信先も異なるため、下表のように役割を分けて運用します。
| 通知の種類 | 送信先 | 実行タイミング | 目的 |
|---|---|---|---|
| 当日リマインド | 打刻漏れの本人 | 21時台(終業後) | その日のうちに打刻し忘れへ気づいてもらう |
| 翌朝サマリー | 管理者 | 8時台(始業前) | 前日分の未対応を管理者側で把握する |
管理者サマリーの送信時刻を始業直後ではなく始業前に設定しているのは、朝の対応方針を決める時間を確保するためです。
打刻漏れの原因が特定の時間帯や特定の従業員に偏っている場合、通知の仕組みだけでなく打刻自体のしやすさを見直す判断材料にもなります。
Google Apps Scriptでの打刻漏れ対応について相談してみませんか?
「本人への通知と管理者への通知をどう使い分ければよいか判断できない」「既存の打刻シートの列構成に合わせて調整したい」「社内にGoogle Apps Scriptを設計できる人材がいない」
少しでもお心当たりがあれば、お気軽にご相談ください。
現在、Google Apps Scriptを使った業務自動化のお悩みをお伺いする 無料の個別相談 を実施しています。
よくある質問
Q. すでに別の列構成で打刻シートを運用している場合はどうすればよいですか?
列の並びをこの記事の4列に合わせて読み替えれば、そのまま使えます。
sheet.getRange(2, 1, lastRow - 1, 4) の取得範囲と const [name, clockIn, clockOut, email] = row; の分割代入部分を、自分のシートの列順に書き換えてください。
Q. 出勤時刻・退勤時刻に日付が含まれていない場合は使えますか?
そのままでは日付の判定ができないため、時刻だけでなく日付も含めて記録する形にシートを見直す必要があります。
時刻のみを記録している場合は、別途「日付」列を追加し、コード側の分割代入と判定式を1列分増やして対応してください。
Q. 本人へのリマインドと管理者サマリーは同じトリガーで動かせますか?
動かせますが、実行時刻の意味合いが異なるためトリガーは分けることをおすすめします。
本人向けは終業直後、管理者向けは翌朝の始業前というように、それぞれの通知が意味を持つ時間帯に合わせて別々のトリガーを設定してください。
Q. 週の合計や月次の集計にも対応できますか?
このコードは当日分と前日分の判定に絞った作りのため、週次・月次の集計には対応していません。
期間をまたいだ集計を行いたい場合は、対象期間の絞り込み条件を別途追加するか、勤怠集計専用の仕組みと組み合わせて運用してください。