appscript.dev
Automation Intermediate Gmail Calendar Docs

Send meeting follow-ups with the notes attached

After a Calendar event ends, email attendees the linked notes Doc automatically.

Published May 19, 2026

The notes from a meeting are only useful if everyone actually gets them. In practice the follow-up depends on whoever took the notes remembering to forward the Doc afterwards — and on a busy day that is exactly the thing that slips. The decisions are written down, they just never reach the people who need them.

At Northwind the fix is a convention plus a script. Whoever runs the meeting pastes the notes Doc link into the Calendar event description. Then, every half hour, this script looks for events that have just ended, finds the Doc link, and emails it to everyone on the guest list. Each event is tagged once it has been handled, so nobody is followed up twice.

What you’ll need

  • A Calendar event whose description contains a link to the notes Doc — a normal https://docs.google.com/document/... URL anywhere in the text.
  • Guests added to the event. The script emails the event’s guest list, so an event with no guests is skipped.
  • The script must run under an account that can read the calendar and its events — by default it uses the owner’s default calendar.

The script

// How far back to look for events that have recently ended.
const LOOKBACK_MS = 60 * 60 * 1000; // one hour

// The event tag used to mark a meeting as already followed up.
const FOLLOWED_UP_TAG = 'followed-up';

// Pattern that matches a Google Docs URL in an event description.
const DOC_URL_PATTERN = /https:\/\/docs\.google\.com\/document\/[^\s<]+/;

/**
 * Finds calendar events that ended within the last hour, and emails
 * the linked notes Doc to every guest. Each handled event is tagged
 * so it is never followed up twice.
 */
function followUpEndedMeetings() {
  // 1. Look at events from the last hour up to now.
  const now = new Date();
  const start = new Date(now.getTime() - LOOKBACK_MS);
  const events = CalendarApp.getDefaultCalendar().getEvents(start, now);

  if (!events.length) {
    Logger.log('No recent events — nothing to do.');
    return;
  }

  for (const event of events) {
    // 2. Skip events that have not actually ended yet.
    if (event.getEndTime() > now) continue;

    // 3. Skip events already followed up on a previous run.
    if (event.getTag(FOLLOWED_UP_TAG) === 'yes') continue;

    // 4. Pull the notes-Doc link out of the event description.
    const match = event.getDescription().match(DOC_URL_PATTERN);
    const docUrl = match ? match[0] : null;
    if (!docUrl) continue;

    // 5. Collect the guest emails; skip the event if there are none.
    const guests = event.getGuestList()
      .map((guest) => guest.getEmail())
      .filter(Boolean);
    if (guests.length === 0) continue;

    // 6. Email everyone the notes link.
    GmailApp.sendEmail(
      guests.join(','),
      `Notes from ${event.getTitle()}`,
      `Thanks for the time today. Notes are here: ${docUrl}\n\n— Northwind`,
    );

    // 7. Tag the event so it is not followed up again.
    event.setTag(FOLLOWED_UP_TAG, 'yes');
    Logger.log(`Followed up "${event.getTitle()}" with ${guests.length} guest(s).`);
  }
}

How it works

  1. followUpEndedMeetings asks the default calendar for every event between one hour ago and now. The one-hour LOOKBACK_MS window comfortably covers a 30-minute trigger interval. If there are no events it logs and stops.
  2. For each event it checks getEndTime(). An event that is still running is skipped — it will be picked up on a later run, after it ends.
  3. It checks the followed-up tag. Tags are key-value labels stored on the event itself, so this is the script’s memory: a meeting marked yes is never processed again.
  4. It searches the event description for a Google Docs URL with DOC_URL_PATTERN. If there is no link, the event is skipped — the script only follows up meetings that actually have notes.
  5. It builds the guest list from getGuestList(), keeping only real email addresses. An event with no guests is skipped.
  6. It sends one email to all guests with the notes link in the body.
  7. It sets the followed-up tag to yes, so the next run leaves this event alone.

Example run

Suppose a meeting “Q3 planning” ended at 2:30pm with this description:

Agenda and running notes:
https://docs.google.com/document/d/1aBcD.../edit

When the trigger fires at 3pm, the script finds the event, extracts the Doc URL, and emails its three guests:

Subject: Notes from Q3 planning

Thanks for the time today. Notes are here: https://docs.google.com/document/d/1aBcD…/edit

— Northwind

The event is tagged followed-up, so the 3:30pm run skips it.

Trigger it

This is a background job, so run it on a time-driven trigger:

  1. In the Apps Script editor open Triggers (the clock icon).
  2. Add a trigger for followUpEndedMeetings, choose Time-driven, then Minutes timer and Every 30 minutes.
  3. Approve the authorisation prompt the first time it runs.

A 30-minute cadence means a follow-up lands within half an hour of the meeting ending, while the followed-up tag keeps overlapping runs from sending twice.

Watch out for

  • The follow-up depends entirely on the convention being kept. If the notes Doc link is not in the event description, the event is silently skipped — no link, no email.
  • Event tags survive on the event, so the script’s memory is durable. But if someone deletes and recreates an event, the new copy has no tag and may be followed up again.
  • Guests are read from the event’s guest list, not from the description. People cc’d informally or invited verbally will not receive the notes.
  • Only the default calendar is checked. Meetings on a secondary or shared calendar are missed unless you point getDefaultCalendar() at the right calendar by ID.
  • An all-day event has an end time of midnight, so it counts as “ended” for most of the day. If all-day events ever carry a notes link, exclude them with event.isAllDayEvent().
  • The email goes out as whoever owns the script and counts against that account’s daily Gmail quota — fine for normal meeting volumes.

Related