Google Apps Script that monitors a shared Google Calendar and sends Slack notifications exactly when LLM-D meetings start. Includes intelligent storage management and comprehensive testing functions.
The script sends notifications when meetings are actually starting:
-
Precise Detection: Runs every minute and looks for meetings starting within ±90 seconds
- Meeting at 2:00:00 PM gets notified between 1:58:30-2:01:30 PM
- Accounts for Google Apps Script trigger timing variations
- No more early notifications - alerts sent when meetings begin
-
Smart Tracking: Prevents duplicate notifications with intelligent record-keeping
- Each meeting gets exactly one notification using unique identifiers
- PropertiesService stores which meetings have been notified
- Automatic cleanup prevents storage overflow
-
Examples:
- Script at 1:59:30 PM → Finds 2:00:00 PM meeting → ✅ NOTIFY (30s early)
- Script at 2:00:15 PM → Finds 2:00:00 PM meeting → ✅ NOTIFY (15s late)
- Script at 2:01:35 PM → Meeting already notified → ⏭️ Skip
- Precise timing: Notifications sent at meeting start time (±90 seconds)
- Canceled meetings filter: Automatically ignores meetings with "Canceled" in the title (case-insensitive)
- Duplicate prevention: Intelligent tracking ensures one notification per meeting
- Storage management: Multi-layered cleanup prevents PropertiesService overflow
- Smart cleanup: Adapts cleanup frequency based on storage usage (4h/8h/24h)
- Daily maintenance: End-of-day cleanup at 11:30 PM removes old records
- Storage monitoring: Health alerts when storage reaches 80% capacity
- Uses Calendar API: Advanced meeting data extraction with conference info
- Document detection: Finds and displays meeting documents with actual file names
- Channel-specific messaging: Different content for SIG vs community channels
- Comprehensive testing: Multiple debug functions for setup and monitoring
- SIG Meetings:
- Posted to the specific SIG channel: "The weekly public llm-d sig-name meeting is starting NOW! Join us!"
- Posted to #community channel: "The weekly public llm-d sig-name meeting is starting NOW! Join the #sig-channel channel for detailed discussion."
- Community Meeting: Posted only to #community channel: "The weekly public llm-d Community Meeting is starting NOW! Join us!"
- Canceled Meetings: Automatically ignored - no notifications sent for meetings with "Canceled" in the title
- Content: Notifications include Google Meet links and attached documents when available
- Timing: All notifications sent exactly when meetings begin, not in advance
The system automatically ignores any meeting with "Canceled" in the title to prevent notifications for canceled events.
- Case-insensitive matching: Detects "Canceled", "canceled", "CANCELED", or any case variation
- Position-independent: Works regardless of where "Canceled" appears in the title
- Early filtering: Canceled meetings are skipped before any processing occurs (efficient)
- Logging: Skipped meetings are logged as
⏭️ Skipping canceled meeting: "meeting title"
✅ These meetings will be ignored:
"Canceled - llm-d Community Meeting""[PUBLIC] llm-d sig-autoscaling - Meeting Canceled""Meeting CANCELED due to holiday""Canceled: [PUBLIC] llm-d sig-benchmarking""sig-observability weekly - canceled this week"
✅ These meetings will still be processed:
"[PUBLIC] llm-d sig-autoscaling: Weekly Planning""[PUBLIC] llm-d Community Meeting""Meeting about cancellation policies"(word "cancellation" is different)
- Google Workspace account with Calendar and Drive access
- Slack workspace with webhook permissions
- Access to the shared Google Calendar you want to monitor
- Google Apps Script project with Calendar API and Drive API enabled
- Configuration matching your meeting organizer setup
- Go to Google Calendar
- Find the shared calendar you want to monitor
- Click the three dots next to the calendar name
- Select "Settings and sharing"
- Scroll down to "Calendar ID" and copy the value (usually in email format)
- Go to script.google.com
- Click "New Project"
- Name it: "LLM-D Calendar Meeting Notifier"
- Delete the default
myFunction()code - Copy and paste the entire contents of
calendar-meeting-notifier.js
-
Enable Google Calendar API:
- In your Google Apps Script project, click the ⚙️ gear icon (Project Settings)
- Go to "Services"
- Click "+ Add a service"
- Find "Google Calendar API" and add it with identifier "Calendar"
-
Enable Google Drive API:
- Click "+ Add a service" again
- Find "Google Drive API" and add it with identifier "Drive"
-
Create config.js in your calendar notifier script:
- In your calendar notifier script, click the "+" next to "Files"
- Add a new script file called
config - Copy the contents from
config.example.jsand update with your values:
const CONFIG = { DEBUG_MODE: false, CALENDAR_ID: 'your-calendar-id@group.calendar.google.com', MEETING_CONFIGS: { '[PUBLIC] llm-d sig-autoscaling': { targetFolderId: 'not-needed-for-calendar', slackWebhook: 'YOUR_SLACK_WEBHOOK_URL', slackChannel: '#sig-autoscaling' }, // ... add all your meeting configurations here ... }, DEFAULT_WEBHOOK: 'YOUR_DEFAULT_WEBHOOK_URL' };
-
Get your calendar ID:
- Go to Google Calendar
- Find your shared calendar
- Click Settings → Calendar settings → Calendar ID
- Copy the ID (usually ends with @group.calendar.google.com)
- In Google Apps Script, run the
debugListUpcomingEvents()function - Grant necessary permissions when prompted:
- Google Calendar: Read calendar events
- External requests: Send Slack notifications
- Check the execution log to see if events are found
- Run
testCalendarNotifier()function for full flow testing - Run
testNextMeetingNotification()to test with your actual next meeting - These run in debug mode and send test messages to your error channel
- Verify the message formatting looks good for both SIG and community channels
- Check that Google Meet links and document attachments are properly formatted
-
Main notification trigger: Run the
setupCalendarTrigger()function once- Creates a trigger to run every minute (ensuring notifications within 90 seconds of meeting start)
- Replaces any existing triggers for the same function
-
Storage management trigger: Run the
setupCleanupTriggers()function once- Creates daily cleanup trigger (runs at 11:30 PM)
- Prevents PropertiesService storage overflow
- Only sends alerts on failures, not successes
-
Both setup functions log confirmation messages
-
Check Google Apps Script triggers:
- Go to "Triggers" in the left sidebar
- Verify there is ONE trigger for
checkCalendarAndNotifythat runs every minute - Verify there is ONE trigger for
dailyCleanupNotificationRecordsthat runs daily at 11:30 PM
-
Monitor storage health: Run
monitorStorageHealth()to check current usage- Should show 0/50 properties initially (healthy storage)
- Will show utilization percentage as meetings get tracked
-
Test live notifications: Wait for meetings to start and verify:
- Notifications appear within 90 seconds of meeting start time
- No duplicate notifications for the same meeting
- Only one notification per meeting across multiple trigger runs
-
Monitor your error channel for:
- Storage warnings (only if issues occur)
- Any processing errors
- Setup confirmations
Configuration & Setup:
testConfig()- Verify configuration is loaded and all required fields are presentsetupCalendarTrigger()- Create the every-minute trigger (run once)setupCleanupTriggers()- Create the daily cleanup trigger (run once)
Meeting Detection & Testing:
testTimingWindow()- Shows how ±90 second timing logic works with examplestestCalendarNotifier()- End-to-end test in debug mode (sends to error channel)debugListUpcomingEvents()- List all events found in current search windowtestNextMeetingNotification()- Find and test your next real meeting (searches 7 days ahead)
Storage Management & Monitoring:
monitorStorageHealth()- Complete storage utilization report with health statuslistNotificationRecords()- Show all current notification tracking recordsclearAllNotificationRecords()- Clear all tracking records (for testing)dailyCleanupNotificationRecords()- Manual daily cleanup (6-hour cutoff)cleanupOldNotificationRecords()- Manual smart cleanup (adaptive timing)
Set DEBUG_MODE: true in your config to:
- Send all notifications to the error channel instead of actual channels
- Add
[DEBUG for #channel-name]prefix to all messages - Test message formatting without spamming channels
-
"CONFIG is not defined"
- Make sure you created a
config.jsfile in your calendar notifier script - Verify the file contains a global
CONFIGvariable - Check that all required fields are included
- Make sure you created a
-
"Calendar not found"
- Verify the
CALENDAR_IDis correct in your config.js - Ensure the calendar is shared with your Google account
- Check that the calendar ID includes the full domain (e.g.,
@group.calendar.google.com)
- Verify the
-
"No meetings starting now"
- Run
debugListUpcomingEvents()to see what events exist - Verify events have titles that match your configured prefixes
- Check that events don't have "Canceled" in the title (these are automatically filtered out)
- Check that events are starting within ±90 seconds of current time
- Use
testTimingWindow()to understand the detection window
- Run
-
"Slack notifications not sent"
- Verify webhook URLs are correct in your config.js
- Check that the DEFAULT_WEBHOOK is working
- Test with
testCalendarNotifier()in debug mode
-
"Permissions denied"
- Re-run functions to grant additional permissions
- Ensure your Google account has access to the calendar
- Check Google Apps Script execution permissions
-
Storage/duplicate notification issues
- Run
monitorStorageHealth()to check PropertiesService usage - If storage is full (near 50/50 properties), run
cleanupOldNotificationRecords() - Use
listNotificationRecords()to see what's being tracked - For testing, use
clearAllNotificationRecords()to reset tracking
- Run
- Error notifications: Automatically sent to your error webhook channel
- Storage health: Monitor with
monitorStorageHealth()function - Execution logs: Use the execution transcript in Google Apps Script for detailed logs
- Quiet operation: System runs silently unless there are issues
- Storage alerts: Only sent when storage reaches 80% or cleanup issues occur
Ensure calendar events use the exact prefixes from your config:
[PUBLIC] llm-d sig-autoscaling: Weekly Planning[PUBLIC] llm-d Community Meeting[PUBLIC] llm-d sig-benchmarking: Sprint Review
Important: Meetings with "Canceled" anywhere in the title are automatically ignored:
Canceled - [PUBLIC] llm-d Community Meeting→ ❌ No notification[PUBLIC] llm-d sig-autoscaling - Meeting Canceled→ ❌ No notification
The script extracts Google Meet links from:
- Conference data (preferred - most reliable)
- Event description (fallback)
- Event location field (fallback)
For best results with document detection:
- Attach documents directly to calendar events when possible
- Include Google Drive links in event descriptions
- Use standard Google Drive sharing links
The script sends notifications exactly when meetings start using precise detection and smart tracking:
- Runs every minute for maximum precision
- ±90 second window: Finds meetings starting within 90 seconds of current time
- No advance notifications: Alerts sent when meetings actually begin, not beforehand
- Accounts for trigger variations: Google Apps Script triggers may run a few seconds early/late
| Current Time | Meeting Start | Seconds Difference | Action | Result |
|---|---|---|---|---|
| 1:58:45 PM | 2:00:00 PM | +75s | ✅ NOTIFY | 1m 15s early |
| 1:59:30 PM | 2:00:00 PM | +30s | ✅ NOTIFY | 30s early |
| 2:00:00 PM | 2:00:00 PM | 0s | ✅ NOTIFY | Exactly on time 🎯 |
| 2:00:45 PM | 2:00:00 PM | -45s | ✅ NOTIFY | 45s late |
| 2:01:30 PM | 2:00:00 PM | -90s | ✅ NOTIFY | 1m 30s late |
| 2:02:00 PM | 2:00:00 PM | -120s | ❌ Skip | Too late (already notified) |
The system prevents duplicate notifications using PropertiesService tracking:
-
Unique Identification: Each meeting gets a unique key based on:
- Calendar event ID
- Meeting start time
- Creates key like:
notified_eventid123_2024-01-15T14:00:00.000Z
-
Smart Tracking: Before sending notifications:
- Check if key exists in PropertiesService
- If exists → Skip (already notified)
- If not → Send notifications and record key
-
Example Flow:
1:59:30 PM → Meeting found → No record → ✅ Send notifications → Record key 2:00:15 PM → Same meeting found → Record exists → ⏭️ Skip notifications 2:00:45 PM → Same meeting found → Record exists → ⏭️ Skip notifications
The system automatically manages tracking records to prevent storage overflow:
Smart Cleanup Frequency:
- < 30 records: 24-hour cleanup (normal load)
- 30-45 records: 8-hour cleanup (moderate load)
- 45+ records: 4-hour cleanup (high load)
- 48+ records: Emergency cleanup (remove oldest immediately)
Daily Maintenance:
- 11:30 PM cleanup: Removes records older than 6 hours
- Health monitoring: Alerts when storage reaches 80% capacity (40/50 properties)
- Automatic alerts: Only sends notifications when there are issues
Running every minute is well within Google Apps Script limits:
| Resource | Daily Limit | Usage (1-min) | Status |
|---|---|---|---|
| Script runtime | 6 hours | ~24 minutes | ✅ 0.7% of limit |
| Triggers | 20 per script | 2 triggers | ✅ 10% of limit |
| Calendar API | 1,000,000 calls | ~1,440 calls | ✅ 0.1% of limit |
| URL fetches | 20,000 calls | Variable | ✅ Safe |
| PropertiesService | 50 properties | Auto-managed | ✅ Monitored |
Verdict: Every minute frequency is perfectly safe and provides optimal precision.
Use these commands to monitor and maintain your system:
// Check current storage usage and health
monitorStorageHealth()
// View all notification tracking records
listNotificationRecords()
// Manual cleanup if needed (emergency use)
cleanupOldNotificationRecords()
// Clear all records (testing only)
clearAllNotificationRecords()Normal Operation: System runs silently with automatic maintenance. You'll only get alerts if there are issues.
The system automatically adapts, but you can adjust timing in the code:
// In cleanupOldNotificationRecords() - line ~460
// Current: 4h/8h/24h adaptive cleanup
if (notificationRecords.length >= 45) {
cutoffHours = 4; // Very aggressive - change to 6 for less aggressive
} else if (notificationRecords.length >= 30) {
cutoffHours = 8; // Moderate - change to 12 for less aggressive
} else {
cutoffHours = 24; // Normal - change to 48 for longer retention
}Recommendation: The default adaptive system works well for most use cases.
- Calendar access is read-only
- Webhook URLs should be kept secure in your
config.js - The script only processes events matching your configured prefixes
- All error notifications include timestamps for audit purposes
If notifications aren't working:
- Check timing: Run
testTimingWindow()to understand detection window - Check configuration: Run
testConfig()to verify all settings - Check meetings: Run
debugListUpcomingEvents()to see what events exist - Check storage: Run
monitorStorageHealth()to verify tracking system - Test end-to-end: Run
testNextMeetingNotification()with real meeting
If getting duplicate notifications or storage errors:
- Check usage:
monitorStorageHealth()shows current utilization - Manual cleanup:
cleanupOldNotificationRecords()for immediate cleanup - View records:
listNotificationRecords()shows what's tracked - Reset for testing:
clearAllNotificationRecords()clears all tracking
- Execution logs: Google Apps Script → Executions tab for detailed error messages
- Error channel: Monitor your DEFAULT_WEBHOOK channel for automated alerts
- Calendar permissions: Ensure your account can read the shared calendar
- Webhook testing: Verify URLs work in debug mode before live deployment
The system is designed to run quietly - you should only get notifications when there are actual issues to address.