appscript.dev
Automation Beginner Calendar Gmail

Nudge organizers of agendaless meetings

Remind Northwind organisers to add details before their event starts.

Published Aug 25, 2025

Half of Northwind’s “what is this meeting about?” anxiety would disappear if organisers added a one-line agenda before clicking send. The studio tried asking nicely on Slack, and the result was patchier than expected — agendas are easy to forget when you’re booking five minutes before stepping into another meeting.

This script reads the calendar window from twelve to thirty-six hours ahead, finds meetings with no description, and emails the organiser a gentle nudge. It tags each event so the nudge is sent exactly once per meeting, which means the trigger can fire every few hours without ever spamming the same person twice for the same event.

What you’ll need

  • Edit access to a Google Calendar (the script uses your default calendar).
  • Permission to send mail as the script owner. The script uses GmailApp and sends from your address.
  • Nothing else.

The script

// How far ahead to start looking, in hours. Twelve hours gives the
// organiser the rest of the working day to respond before the meeting.
const LOOKAHEAD_START_HOURS = 12;
// How far ahead to stop looking, in hours. Beyond 36 hours people start
// adding agendas naturally, and earlier nudges feel pushy.
const LOOKAHEAD_END_HOURS = 36;

// Tag key written on each event after a nudge is sent. Tags are
// per-calendar key/value strings — perfect for "did I already do X?".
const NUDGE_TAG_KEY = 'agenda-nudged';
const NUDGE_TAG_VALUE = 'yes';

/**
 * Looks at meetings 12-36 hours out and emails the organiser if the
 * event has no description. Each meeting is nudged at most once,
 * tracked via an event tag.
 */
function nudgeAgendaless() {
  // 1. Build the lookahead window. Multiplying milliseconds is fine
  //    for a short window like this.
  const start = new Date(Date.now() + LOOKAHEAD_START_HOURS * 3_600_000);
  const end = new Date(Date.now() + LOOKAHEAD_END_HOURS * 3_600_000);
  const events = CalendarApp.getDefaultCalendar().getEvents(start, end);

  let nudged = 0;
  for (const event of events) {
    // 2. Skip events that already have an agenda. Any non-whitespace
    //    text in the description counts — even one line is enough.
    if ((event.getDescription() || '').trim()) continue;

    // 3. Skip events that have already been nudged. Tags persist on
    //    the event itself, so the check survives across script runs.
    if (event.getTag(NUDGE_TAG_KEY) === NUDGE_TAG_VALUE) continue;

    // 4. The organiser is the first creator. Recurring instances
    //    inherit the original creator, which is usually what we want.
    const organiser = event.getCreators()[0];
    if (!organiser) continue;

    GmailApp.sendEmail(
      organiser,
      'Add an agenda? ' + event.getTitle(),
      'Your meeting "' + event.getTitle()
        + '" has no agenda. Adding one — even a single line —'
        + ' saves everyone time and lets people prepare.\n\n'
        + 'You can edit the meeting in Google Calendar to add a description.'
    );

    // 5. Mark the event so the next trigger pass leaves it alone.
    event.setTag(NUDGE_TAG_KEY, NUDGE_TAG_VALUE);
    nudged++;
  }

  Logger.log('Nudged ' + nudged + ' organisers.');
}

How it works

  1. The script builds a start/end window 12 to 36 hours into the future. Anything sooner is too late for a polite ask; anything further out is too early to feel urgent.
  2. getEvents returns every event in the window, recurring instances expanded. The loop trims out the ones that already have an agenda — a non-empty getDescription() is enough to mean “the organiser thought about it”.
  3. The per-event tag agenda-nudged is the idempotence guard. Tags are stored on the event itself, so they survive across trigger runs without needing a separate datastore.
  4. The organiser comes from event.getCreators()[0]. For most events that is the person who originally created the meeting; for events inherited from another calendar this can be empty, in which case the loop simply moves on.
  5. GmailApp.sendEmail sends from the script owner’s address. After the send, the tag is written so the next run sees the event as already handled.

Example run

Suppose the script fires at 09:00 on a Monday. The events in the 12-36 hour window look like this:

EventDescriptionAction
Tue 09:30 Design review”Discuss colour palette”Skipped — has agenda
Tue 11:00 Client call(empty)Nudge sent, tag written
Tue 14:00 Standup(empty, but already tagged)Skipped — already nudged
Tue 16:00 Quick sync(empty)Nudge sent, tag written

Two emails are sent. When the trigger fires again at 15:00, both newly nudged events already carry the tag, so nothing further is sent.

Trigger it

The natural cadence is every few hours during working time:

  1. In the Apps Script editor, open Triggers.
  2. Add a trigger for nudgeAgendaless, event source Time-driven, type Hours timer, every 6 hours.

A 6-hour timer hits the window often enough to catch late-booked meetings, without burning through the daily Gmail quota on a busy calendar.

Watch out for

  • Tags only exist on events that belong to the calendar reading them. If you switch from getDefaultCalendar() to a different calendar where the script account is only a guest, setTag throws — read your own calendar or own the events directly.
  • getCreators() returns the address that originally created the event. For meetings forwarded into your calendar, that may be a colleague — they receive the nudge, which is usually correct. For all-day events imported from another system, the creator can be blank; the script skips those.
  • Description-only is a crude check. A meeting whose agenda lives in a linked Doc but has an empty description gets nudged. If your team links rather than describes, also check for attachments via event.getAttachments().
  • Once tagged, an event is never nudged again — even if the organiser removes the description later. That is the right default for a polite reminder, but if you want stronger enforcement, drop the tag and accept the duplicate emails.

Related