Automating Weekly Calendar Reminders with Google Apps Script and Slack

7 min read
GASSlackAutomation

Table of Contents

The Problem

Every week, our team needed reminders about upcoming events from a shared Google Calendar. Someone would have to manually check the calendar, figure out who was involved, and post a message in Slack with all the relevant details and mentions.

This took 15-20 minutes every Monday morning and was easy to forget. I wanted to automate this completely using Google Apps Script (GAS) since it integrates natively with Google Calendar and can send webhooks to Slack.

Solution Overview

The automation does three things:

  1. Reads calendar events for the current week (Monday to Friday)
  2. Filters events based on keywords in the title
  3. Posts to Slack with formatted messages and user mentions

No external services needed. Just GAS, Google Calendar API, and Slack webhooks.

Setting Up the Slack Webhook

First, create an incoming webhook in Slack:

  1. Go to your Slack workspace's App settings
  2. Create a new app or use an existing one
  3. Enable "Incoming Webhooks"
  4. Add a new webhook to your target channel
  5. Copy the webhook URL (looks like https://hooks.slack.com/services/...)

The Code

Configuration and Data

I started by defining a mapping between email addresses and Slack user IDs. This lets the bot mention the right people when posting reminders:

1const WEBHOOK_URL = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
2const CALENDAR_ID = 'your-calendar-id@group.calendar.google.com'
3
4const memberList = [
5 { user_name: 'alice@example.com', user_id: 'U123456' },
6 { user_name: 'bob@example.com', user_id: 'U789012' },
7 { user_name: 'charlie@example.com', user_id: 'U345678' },
8]

Getting Monday of the Current Week

Calendar events are filtered by week, so I needed a helper function to find the Monday of any given date:

1function getMonday(date) {
2 const d = new Date(date)
3 const day = d.getDay()
4 const diff = d.getDate() - day + (day === 0 ? -6 : 1) // Adjust if Sunday
5 return new Date(d.setDate(diff))
6}

This handles the edge case where the current day is Sunday by going back 6 days instead of forward 1.

Main Function: Reading Events and Posting to Slack

The main function fetches events from the calendar, filters them, formats the data, and sends it to Slack:

1function sendWeeklyCalendarReminder() {
2 // Calculate the week range (Monday 00:00 to Friday 23:59)
3 const startTime = getMonday(new Date().setHours(0, 0, 0, 0))
4 const endTime = new Date(startTime.getTime() + 5 * 24 * 60 * 60 * 1000) // +5 days
5
6 // Get the calendar and its events
7 const calendar = CalendarApp.getCalendarById(CALENDAR_ID)
8 const events = calendar.getEvents(startTime, endTime)
9 const calendarColor = calendar.getColor()
10
11 const eventDetails = []
12
13 // Process each event
14 for (let event of events) {
15 const title = event.getTitle()
16
17 // Filter: only include events with specific keywords
18 if (
19 title.includes('deploy') &&
20 !title.includes('rollback') &&
21 !title.includes('test')
22 ) {
23 const startDate = event.getStartTime()
24 const endDate = event.getEndTime()
25
26 // Format dates in Japanese locale
27 const formattedStart = startDate.toLocaleDateString('ja-JP', {
28 weekday: 'short',
29 month: 'short',
30 day: 'numeric',
31 hour: 'numeric',
32 minute: 'numeric',
33 })
34
35 const formattedEnd = endDate.toLocaleDateString('ja-JP', {
36 weekday: 'short',
37 month: 'short',
38 day: 'numeric',
39 hour: 'numeric',
40 minute: 'numeric',
41 })
42
43 // Get attendees and map to Slack user IDs
44 const guests = event
45 .getGuestList(true)
46 .map((guest) => guest.getEmail())
47 .filter((email) => email.includes('@example.com'))
48
49 const mentions = guests
50 .map((email) => {
51 const member = memberList.find((m) => m.user_name === email)
52 return member ? `<@${member.user_id}>` : null
53 })
54 .filter(Boolean)
55 .join(' ')
56
57 // Build event summary
58 eventDetails.push(
59 `\n\n${mentions}\n${title}\n${formattedStart}${formattedEnd}`,
60 )
61 }
62 }
63
64 // Prepare Slack message payload
65 const payload = {
66 channel: '#team-notifications',
67 username: 'Calendar Bot',
68 icon_emoji: ':calendar:',
69 text:
70 '<!channel> Weekly deployment reminders:\n' +
71 'Please check the calendar and complete your tasks by Thursday.\n' +
72 'If you have any blockers, notify the team lead.',
73 attachments: [
74 {
75 color: calendarColor,
76 fields: [
77 {
78 title: "This Week's Events",
79 value: eventDetails.join('\n') || 'No events this week',
80 short: false,
81 },
82 ],
83 },
84 ],
85 }
86
87 sendToSlack(payload)
88}

Sending Data to Slack

The webhook sender is straightforward. It serializes the payload as JSON and posts it using UrlFetchApp:

1function sendToSlack(payload) {
2 const options = {
3 method: 'post',
4 contentType: 'application/json',
5 payload: JSON.stringify(payload),
6 }
7
8 UrlFetchApp.fetch(WEBHOOK_URL, options)
9}

Setting Up the Trigger

To run this automatically every Monday morning:

  1. Open your script in Google Apps Script editor
  2. Go to Triggers (clock icon on the left)
  3. Click Add Trigger
  4. Set:
    • Function: sendWeeklyCalendarReminder
    • Event source: Time-driven
    • Type: Week timer
    • Day: Monday
    • Time: 8am to 9am (or whenever you prefer)

GAS will now run this function every Monday automatically.

Key Improvements I Made

1. User Mentions

The original script just listed names. I added Slack user ID mapping so people actually get notified when mentioned.

2. Better Date Filtering

Instead of hardcoding date ranges, the script calculates the current week dynamically using getMonday(). This works reliably across months and years.

3. Cleaner Event Filtering

Used a clear inclusion/exclusion pattern with keywords instead of chaining multiple conditions:

1if (
2 title.includes("deploy") &&
3 !title.includes("rollback") &&
4 !title.includes("test")
5)

This makes it easy to adjust which events get included.

4. Fallback for Missing Members

If someone isn't in the memberList, the script skips them instead of breaking:

1.filter(Boolean) // Remove null values

Debugging Tips

Check Calendar Permissions

Make sure your script has access to the calendar. Run this manually first:

1function testCalendarAccess() {
2 const calendar = CalendarApp.getCalendarById(CALENDAR_ID)
3 Logger.log(calendar.getName())
4}

If it throws an error, share the calendar with your GAS project's email (found under Project SettingsService account).

Test the Webhook

Send a simple test message to verify the webhook works:

1function testSlackWebhook() {
2 const payload = {
3 text: 'Test message from GAS',
4 }
5 sendToSlack(payload)
6}

View Logs

Use Logger.log() to debug and check execution logs:

  • Click Executions in the left sidebar
  • Click on any execution to see logs and errors

Lessons Learned

1. GAS Date Handling is Quirky

JavaScript's Date object in GAS behaves slightly differently than in browsers. Always test date calculations manually before setting up triggers.

2. Slack Mentions Require User IDs

You can't mention people by email or display name. You need their Slack user ID, which looks like U123ABC456. Get these from Slack's user profile or API.

3. Color Codes Work in Attachments

Using calendar.getColor() lets the Slack message match your calendar's color scheme. Small touch, but makes the bot feel more integrated.

4. Filtering Saves Noise

Not every calendar event needs a reminder. Adding keyword filters keeps the Slack channel focused and reduces notification fatigue.

Impact

This bot has been running every Monday for months now:

  • Saves 15-20 minutes of manual work per week
  • Zero missed reminders since automation started
  • Better visibility with automatic user mentions
  • Lower cognitive load — no one needs to remember to check the calendar

Pro Tips

Use getGuestList(true) to include the organizer in the guest list. Otherwise, you might miss mentioning the person who created the event.

Test with workflow_dispatch-style manual runs before setting up weekly triggers. You don't want to spam your Slack channel while debugging.

Store sensitive data in Script Properties instead of hardcoding it. Go to Project SettingsScript Properties and access via PropertiesService:

1const WEBHOOK_URL =
2 PropertiesService.getScriptProperties().getProperty('WEBHOOK_URL')

Format Slack messages with Block Kit for richer layouts. Check out Slack's Block Kit Builder for interactive message designs.

Wrapping Up

Google Apps Script is underrated for simple automations like this. No servers, no containers, no deployment pipelines. Just write a function, set a trigger, and it runs forever.

This calendar-to-Slack bot took about an hour to build and has saved dozens of hours since. If your team uses Google Calendar and Slack, this pattern works for almost any recurring reminder workflow.

Related Articles