Every Monday, totals the start and finish times your team logs in a sheet and emails each person's hours to a manager or payroll.

UsesGmailGoogle Sheets

Email a weekly summary of everyone's hours

Every Monday at 07:00, the workflow reads the clock-in and clock-out times your team logs on a 'Time entries' tab, keeps those from the previous seven days, works out the hours in each and totals them per person. The totals are emailed to your manager or payroll contact and added as one row per person to a 'Weekly summary' tab, so nobody adds up a spreadsheet by hand.

A shift that runs past midnight, such as 22:00 to 06:00, is counted as an overnight shift. An entry that cannot be read is reported, never counted as zero: a missing name, a badly written time, or a finish before the start is listed with its row number. An entry whose date can't be read is listed once, in the week it first appears. If no entries are dated in the week, you still get an email saying so, and if a column heading is missing the run stops and says which one, so a broken sheet never looks like a quiet week. Your entries are only ever read, never edited.

You need Google Sheets and Gmail connected, the link to your timesheet and the address the summary goes to. The entries tab needs 'Date', 'Person', 'Start' and 'End' headings. It records only what people report about themselves and asks for no location or device data.

ScheduleRun every Monday at 07:00.Run every Monday at 07:00.
ValuesSet spreadsheetUrl to blank.Set spreadsheetUrl to blank.
ValuesSet entriesTab to Time entries.Set entriesTab to Time entries.
ValuesSet summaryTab to Weekly summary.Set summaryTab to Weekly summary.
ValuesSet managerEmail to blank.Set managerEmail to blank.
ValuesSet daysToInclude to 7.Set daysToInclude to 7.
Dates & TimesGet today's date and save it as today.Get today's date and save it as today.
Dates & TimesGet yesterday's date and save it as weekEnding.Get yesterday's date and save it as weekEnding.
Dates & TimesMake weekEnding readable as “November 2, 2026” and save it as weekEndingText.Make weekEnding readable as “November 2, 2026” and save it as weekEndingText.
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as timesheet.Read the rows of spreadsheetUrl. Save it as timesheet.
ListsKeep each item in [Date, Person, Start, End] where timesheet.headers doesn't contain item and save it as missingColumns.Keep each item in [Date, Person, Start, End] where timesheet.headers doesn't contain item and save it as missingColumns.
Decisions & LoopsIf missingColumns isn't emptyIf missingColumns isn't empty
Yes
TextJoin missingColumns with , and save it as missingList.Join missingColumns with , and save it as missingList.
Decisions & LoopsStop and report a problem: The '@entriesTab' tab is missing these column headings: missingList. Row 1 needs Date, Person, Start and End, spelled exactly like that. Nothing was added to the summary or emailed.Stop and report a problem: The '@entriesTab' tab is missing these column headings: missingList. Row 1 needs Date, Person, Start and End, spelled exactly like that. Nothing was added to the summary or emailed.
No
No steps
ValuesSet entries to timesheet.rows.Set entries to timesheet.rows.
ValuesSet workedEntries to an empty list.Set workedEntries to an empty list.
ValuesSet flaggedEntries to an empty list.Set flaggedEntries to an empty list.
ValuesSet peopleSeen to an empty list.Set peopleSeen to an empty list.
Stored ValuesGet the stored value 'timesheet_unreadable_rows'. Save it as unreadableStore.Get the stored value 'timesheet_unreadable_rows'. Save it as unreadableStore.
ValuesSet reportedBefore to an empty list.Set reportedBefore to an empty list.
Decisions & LoopsIf unreadableStore.found is yesIf unreadableStore.found is yes
Yes
ValuesChange reportedBefore to unreadableStore.value.Change reportedBefore to unreadableStore.value.
No
No steps
ValuesSet unreadableNow to an empty list.Set unreadableNow to an empty list.
Decisions & LoopsFor each entry in entries:For each entry in entries:
Decisions & LoopsIf entry.Date is blank and entry.Person is blank and entry.Start is blank and…If entry.Date is blank and entry.Person is blank and entry.Start is blank and…
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet personLabel to Unnamed.Set personLabel to Unnamed.
Decisions & LoopsIf a step below fails, save the error as personErrorIf a step below fails, save the error as personError
Try
TextTrim entry.Person and save it as personLabel.Trim entry.Person and save it as personLabel.
On error
ValuesChange personLabel to Unnamed.Change personLabel to Unnamed.
Decisions & LoopsIf personLabel is emptyIf personLabel is empty
Yes
ValuesChange personLabel to Unnamed.Change personLabel to Unnamed.
No
No steps
ValuesSet rowLabel to ?.Set rowLabel to ?.
Decisions & LoopsIf entry.row_number isn't emptyIf entry.row_number isn't empty
Yes
ValuesChange rowLabel to entry.row_number.Change rowLabel to entry.row_number.
No
No steps
ValuesSet dateReadable to false.Set dateReadable to false.
ValuesSet inThisWeek to false.Set inThisWeek to false.
ValuesSet entryHours to 0.Set entryHours to 0.
ValuesSet entryUsable to false.Set entryUsable to false.
ValuesSet problem to Date, Start or End is missing or is not written as YYYY-MM-DD and HH:MM.Set problem to Date, Start or End is missing or is not written as YYYY-MM-DD and HH:MM.
Decisions & LoopsIf a step below fails, save the error as entryErrorIf a step below fails, save the error as entryError
Try
TextTrim entry.Date and save it as entryDate.Trim entry.Date and save it as entryDate.
Dates & TimesCount the days from entryDate to today and save it as entryAgeInDays.Count the days from entryDate to today and save it as entryAgeInDays.
ValuesChange dateReadable to true.Change dateReadable to true.
ValuesChange inThisWeek to entryAgeInDays is greater than or equal to 1 and entryAgeInDays is less than or equal to daysToInclude.Change inThisWeek to entryAgeInDays is greater than or equal to 1 and entryAgeInDays is less than or equal to daysToInclude.
Dates & TimesCount the minutes from entryDate entry.Start to entryDate entry.End and save it as minutesWorked.Count the minutes from entryDate entry.Start to entryDate entry.End and save it as minutesWorked.
Decisions & LoopsIf minutesWorked is less than or equal to -600If minutesWorked is less than or equal to -600
Yes
ValuesAdd 1440 to minutesWorked.Add 1440 to minutesWorked.
No
No steps
MathRound minutesWorked divided by 60 to 2 decimal places and save it as entryHours.Round minutesWorked divided by 60 to 2 decimal places and save it as entryHours.
ValuesChange entryUsable to true.Change entryUsable to true.
On error
ValuesChange entryUsable to false.Change entryUsable to false.
Decisions & LoopsIf entryUsable is yes and entryHours is less than or equal to 0If entryUsable is yes and entryHours is less than or equal to 0
Yes
ValuesChange entryUsable to false.Change entryUsable to false.
ValuesChange problem to End is not later than Start.Change problem to End is not later than Start.
No
No steps
ValuesSet includeEntry to inThisWeek.Set includeEntry to inThisWeek.
Decisions & LoopsIf dateReadable is noIf dateReadable is no
Yes
TextBuild the text row @rowLabel: @entry.Person | @entry.Date | @entry.Start | @entry.End and save it as rowKey.Build the text row @rowLabel: @entry.Person | @entry.Date | @entry.Start | @entry.End and save it as rowKey.
ListsAdd rowKey to the end of unreadableNow and save it as unreadableNow.Add rowKey to the end of unreadableNow and save it as unreadableNow.
Decisions & LoopsIf reportedBefore doesn't contain rowKeyIf reportedBefore doesn't contain rowKey
Yes
ValuesChange includeEntry to true.Change includeEntry to true.
No
No steps
No
No steps
Decisions & LoopsIf includeEntry is yesIf includeEntry is yes
Yes
ListsAdd personLabel to the end of peopleSeen and save it as peopleSeen.Add personLabel to the end of peopleSeen and save it as peopleSeen.
Decisions & LoopsIf entryUsable is yesIf entryUsable is yes
Yes
ListsAdd a record of person and hours to the end of workedEntries and save it as workedEntries.Add a record of person and hours to the end of workedEntries and save it as workedEntries.
No
ListsAdd a record of person, row, and problem to the end of flaggedEntries and save it as flaggedEntries.Add a record of person, row, and problem to the end of flaggedEntries and save it as flaggedEntries.
No
No steps
ListsRemove duplicates from peopleSeen and save it as people.Remove duplicates from peopleSeen and save it as people.
Decisions & LoopsIf people is emptyIf people is empty
Yes
GmailGmailSend an email to managerEmail.Send an email to managerEmail. Subject: Timesheet summary for the week ending weekEndingText: no entries foundNo time entries dated in the week ending weekEndingText were found on the '@entriesTab' tab, so there are no hours to report. If your team did log hours that week, check that each row's Date is written as YYYY-MM-DD.
Decisions & LoopsFinish the run: No time entries dated in the week ending weekEndingText, so nothing was added to the summary. Emailed managerEmail to say so.Finish the run: No time entries dated in the week ending weekEndingText, so nothing was added to the summary. Emailed managerEmail to say so.
No
No steps
ValuesSet summaryLines to an empty list.Set summaryLines to an empty list.
ValuesSet entriesToCheck to 0.Set entriesToCheck to 0.
ValuesSet summaryTabProblem to blank.Set summaryTabProblem to blank.
Decisions & LoopsFor each person in people:For each person in people:
ValuesSet personHours to an empty list.Set personHours to an empty list.
ValuesSet personProblems to an empty list.Set personProblems to an empty list.
Decisions & LoopsFor each workedEntry in workedEntries:For each workedEntry in workedEntries:
Decisions & LoopsIf workedEntry.person is personIf workedEntry.person is person
Yes
ListsAdd workedEntry.hours to the end of personHours and save it as personHours.Add workedEntry.hours to the end of personHours and save it as personHours.
No
No steps
Decisions & LoopsFor each flaggedEntry in flaggedEntries:For each flaggedEntry in flaggedEntries:
Decisions & LoopsIf flaggedEntry.person is personIf flaggedEntry.person is person
Yes
ListsAdd row flaggedEntry.row - flaggedEntry.problem to the end of personProblems and save it as personProblems.Add row flaggedEntry.row - flaggedEntry.problem to the end of personProblems and save it as personProblems.
No
No steps
MathRound the total of personHours to 2 decimal places and save it as totalHours.Round the total of personHours to 2 decimal places and save it as totalHours.
TextJoin personProblems with ; and save it as problemText.Join personProblems with ; and save it as problemText.
MathChange entriesToCheck to entriesToCheck plus the length of personProblems.Change entriesToCheck to entriesToCheck plus the length of personProblems.
Decisions & LoopsIf a step below fails, save the error as appendErrorIf a step below fails, save the error as appendError
Try
Google SheetsGoogle SheetsAdd a row to summaryTab in spreadsheetUrl.Add a row to summaryTab in spreadsheetUrl.
On error
ValuesChange summaryTabProblem to appendError.message.Change summaryTabProblem to appendError.message.
ValuesSet summaryLine to person: totalHours hours from the length of personHours entries.Set summaryLine to person: totalHours hours from the length of personHours entries.
Decisions & LoopsIf personProblems isn't emptyIf personProblems isn't empty
Yes
ValuesChange summaryLine to summaryLine - NEEDS ATTENTION: problemText.Change summaryLine to summaryLine - NEEDS ATTENTION: problemText.
No
No steps
ListsAdd summaryLine to the end of summaryLines and save it as summaryLines.Add summaryLine to the end of summaryLines and save it as summaryLines.
GmailGmailSend an email to managerEmail.Send an email to managerEmail. Subject: Timesheet summary for the week ending weekEndingTextSelf-reported hours for the week ending weekEndingText. summaryLines joined by a new line Entries needing attention: entriesToCheck The full breakdown is on the '@summaryTab' tab of the timesheet.
Decisions & LoopsIf a step below fails, save the error as saveErrorIf a step below fails, save the error as saveError
Try
Stored ValuesSet the stored value 'timesheet_unreadable_rows' to unreadableNow.Set the stored value 'timesheet_unreadable_rows' to unreadableNow.
On error
Decisions & LoopsStop and report a problem: The summary was emailed, but Lodol could not note which entries with an unreadable date it has reported (saveError.message), so they will be listed again next week.Stop and report a problem: The summary was emailed, but Lodol could not note which entries with an unreadable date it has reported (saveError.message), so they will be listed again next week.
Decisions & LoopsIf summaryTabProblem isn't blankIf summaryTabProblem isn't blank
Yes
Decisions & LoopsStop and report a problem: The summary was emailed to managerEmail, but rows could not be added to the '@summaryTab' tab (summaryTabProblem). Check that the tab exists with that exact name.Stop and report a problem: The summary was emailed to managerEmail, but rows could not be added to the '@summaryTab' tab (summaryTabProblem). Check that the tab exists with that exact name.
No
No steps
Decisions & LoopsFinish the run: Emailed the summary for the week ending weekEndingText to managerEmail and added one row per person to the '@summaryTab' tab.Finish the run: Emailed the summary for the week ending weekEndingText to managerEmail and added one row per person to the '@summaryTab' tab.