appscript.dev
Automation Beginner Calendar Gmail

Email a clean daily agenda each morning

Send Awadesh a list of today's Northwind events at 7am — no app needed.

Published Sep 22, 2025

The Google Calendar app already shows today’s events, but only when you open it. Awadesh prefers to skim the day over coffee, in the same inbox where the rest of the work waits. The agenda used to live on a sticky note copied from the calendar the night before, which fell over the first time a meeting moved overnight.

This script reads today’s calendar, formats the events as a tidy time-and-title list, and emails the result to the script owner. Running it at 7am gives every morning a single, readable summary of what is coming — no app to open, no sticky note to update.

What you’ll need

  • A Google account where the script can read your default calendar and send mail as you. The script uses Session.getActiveUser(), so it always emails whoever the script runs as.
  • Nothing else. No spreadsheet, no external API, no add-on.

The script

// The time zone used to format event start times. Falls back to the
// script project's time zone, which is itself set under File → Project
// settings.
const TIME_ZONE = Session.getScriptTimeZone();

// Format string for the start-time column in the agenda body.
const TIME_FORMAT = 'HH:mm';

/**
 * Reads today's events from the default calendar and emails the
 * active user a one-line-per-event summary. Quietly does nothing on
 * empty days so a free Saturday is not announced.
 */
function dailyAgenda() {
  // 1. Build a "today" window that runs from midnight to one tick before
  //    midnight tomorrow. Half-open ranges would also work, but the
  //    inclusive end makes all-day events at 23:59 reliable.
  const start = new Date();
  start.setHours(0, 0, 0, 0);
  const end = new Date(start);
  end.setHours(23, 59, 59, 999);

  // 2. Pull every event whose interval touches today. CalendarApp
  //    returns recurring instances expanded — no extra work needed.
  const events = CalendarApp.getDefaultCalendar().getEvents(start, end);
  if (events.length === 0) {
    Logger.log('No events today — no email sent.');
    return;
  }

  // 3. Format each event as "HH:mm  Title". Two spaces line up nicely
  //    in a fixed-width or monospace inbox preview.
  const body = events
    .map((e) => {
      const time = e.isAllDayEvent()
        ? 'all day'
        : Utilities.formatDate(e.getStartTime(), TIME_ZONE, TIME_FORMAT);
      return time + '  ' + e.getTitle();
    })
    .join('\n');

  // 4. Mail it to the script owner. `Session.getActiveUser()` returns
  //    the address of whoever the trigger runs as.
  const subject = 'Today — ' + events.length + ' event'
    + (events.length === 1 ? '' : 's');
  GmailApp.sendEmail(Session.getActiveUser().getEmail(), subject, body);
}

How it works

  1. The script builds a window from midnight today to 23:59:59.999 today, so any event whose start lies on the calendar day is captured — including all-day events.
  2. getEvents on the default calendar returns the matching events, expanded from any recurring series. If the day is empty, the script logs and exits without sending.
  3. Each event is formatted as HH:mm Title, with all-day events labelled all day instead of a time. Joining with newlines keeps the email body in plain text, which renders predictably across clients.
  4. The subject line counts the events (“Today — 4 events”) so the inbox preview is informative even before the email is opened. The mail goes to the active user — usually the script owner.

Example run

A typical Tuesday calendar produces an email like this:

Subject: Today — 4 events

all day  Deep work day — no meetings
09:30  Standup
11:00  Client call — Priya
15:00  Design review

On a Saturday with nothing booked, the inbox stays quiet — the script returns before it ever calls GmailApp.sendEmail.

Trigger it

This is a textbook scheduled job:

  1. In the Apps Script editor, open Triggers.
  2. Add a new trigger for dailyAgenda, event source Time-driven, type Day timer, time of day 7am to 8am.
  3. Approve the prompt — the script needs Calendar read access and Gmail send access the first time it runs.

If you want the agenda to lag the day instead of leading it, change the trigger to fire late evening — the same code reads “today” relative to when it runs.

Watch out for

  • The time zone of the formatted output is the script project’s, not your device’s. Open File → Project settings if Monday’s 09:00 standup is showing up as 08:00 in the email.
  • “Today” is whatever day the trigger fires on. If the script runs at 23:55 for any reason, the events listed are still that day’s, but at 00:05 they would be the next day’s. A 7am trigger sidesteps the edge case entirely.
  • Long event titles can wrap awkwardly in some mail clients because the body is plain text. Swap the formatter for HTML if you need columns to align — GmailApp.sendEmail accepts an htmlBody option for that.
  • The script reads only your default calendar. If your day spans several calendars (personal plus team plus client), call getAllOwnedCalendars(), merge their events, and sort by start time before formatting.

Related