Apps Script example · 10 min read

Overtime Alert: Copy-Paste Apps Script Pattern

Working overtime alert example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

HRMailAppOvertime

Overtime risk should surface before payroll closes. A weekly rollup with one email per employee-week keeps noise down.

overtimeAlert aggregates hours by employee|ISO-week, and when totals exceed OT_LIMIT sends MailApp to MANAGER.

Column D stores the weekKey after alerting so repeats do not spam.

Adjust OT_LIMIT for local labor rules; this is not legal advice.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

SheetColumnPurpose
TimesheetsA employeeWorker key
TimesheetsB dateWork date
TimesheetsC hoursDecimal hours
TimesheetsD alertedWeekKeyDedupe marker
OT_LIMIT40Weekly threshold
MANAGERmanagers@…Alert destination

What this script does

Weekly overtime detection with one email per employee-week.

Prerequisites

Hours calculated; MailApp auth; manager inbox.

  • Date column real Dates
  • OT_LIMIT set
  • Run after hours calc

Walkthrough

Create >40 hours in one ISO week; run; receive one email; rerun silent.

Edge cases

ISO week boundaries may differ from your payroll week — swap isoWeekKey_ if needed.

  • alertedWeekKey prevents spam
  • Multiple rows share one email

How to test

Two employees over limit → two emails.

Hardening for production

CC employee; write OvertimeLog; use Chat.

Variations

Daily OT over 8 hours instead of weekly 40.

Full code: overtimeAlert()

Run timesheetHoursCalculation first, then overtimeAlert() on a daily trigger near week end.

/**
 * Email managers when an employee exceeds weekly overtime hours.
 */
const TS = "Timesheets"; // A employee, B date, C hours, D alertedWeekKey
const OT_LIMIT = 40;
const MANAGER = "managers@example.com";

function overtimeAlert() {
  const sheet = SpreadsheetApp.getActive().getSheetByName(TS);
  const values = sheet.getDataRange().getValues();
  const totals = {};
  const weekKeys = {};

  for (let i = 1; i < values.length; i++) {
    const emp = values[i][0];
    const dt = values[i][1];
    const hours = Number(values[i][2]) || 0;
    if (!(dt instanceof Date)) continue;
    const weekKey = emp + "|" + isoWeekKey_(dt);
    totals[weekKey] = (totals[weekKey] || 0) + hours;
    weekKeys[i] = weekKey;
  }

  const alerted = {};
  for (let i = 1; i < values.length; i++) {
    const weekKey = weekKeys[i];
    if (!weekKey) continue;
    if (totals[weekKey] <= OT_LIMIT) continue;
    if (values[i][3] === weekKey) continue; // row already marked for this week alert
    if (alerted[weekKey]) {
      values[i][3] = weekKey;
      continue;
    }
    const emp = values[i][0];
    MailApp.sendEmail(
      MANAGER,
      "Overtime alert: " + emp,
      emp + " has " + totals[weekKey].toFixed(2) + " hours in week " + weekKey.split("|")[1] +
        " (limit " + OT_LIMIT + ")."
    );
    alerted[weekKey] = true;
    values[i][3] = weekKey;
  }
  sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}

function isoWeekKey_(date) {
  const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
  const dayNum = d.getUTCDay() || 7;
  d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
  const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
  return d.getUTCFullYear() + "-W" + String(weekNo).padStart(2, "0");
}
  1. Line 5: Weekly hours threshold.
  2. Line 19: Builds an employee-independent week id.
  3. Line 20: Aggregates hours per employee-week.
  4. Line 35: Manager notification.
  5. Line 4: Column D dedupe stamp.
  6. Line 30: Ensures one email even across many rows.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before you run: overtime alert

  • 1Hours column populated
  • 2OT_LIMIT matches policy
  • 3MANAGER address monitored
  • 4Payroll week definition checked vs ISO
  • 5MailApp authorized
  • 6Dedupe column blank for new weeks

Frequently asked questions

Replace isoWeekKey_ with a custom week-start calculator.

Aggregate by employee|date and compare to 8.

Filter employees via a roster sheet before totaling.

So any row in that week shows the alert was sent.

Unnecessary if weekKey changes each week.

toFixed(2) is display-only in the email; totals use raw numbers.

One mail per over-limit employee-week is usually fine.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.