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:
- Reads calendar events for the current week (Monday to Friday)
- Filters events based on keywords in the title
- 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:
- Go to your Slack workspace's App settings
- Create a new app or use an existing one
- Enable "Incoming Webhooks"
- Add a new webhook to your target channel
- 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'34const 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 Sunday5 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 days56 // Get the calendar and its events7 const calendar = CalendarApp.getCalendarById(CALENDAR_ID)8 const events = calendar.getEvents(startTime, endTime)9 const calendarColor = calendar.getColor()1011 const eventDetails = []1213 // Process each event14 for (let event of events) {15 const title = event.getTitle()1617 // Filter: only include events with specific keywords18 if (19 title.includes('deploy') &&20 !title.includes('rollback') &&21 !title.includes('test')22 ) {23 const startDate = event.getStartTime()24 const endDate = event.getEndTime()2526 // Format dates in Japanese locale27 const formattedStart = startDate.toLocaleDateString('ja-JP', {28 weekday: 'short',29 month: 'short',30 day: 'numeric',31 hour: 'numeric',32 minute: 'numeric',33 })3435 const formattedEnd = endDate.toLocaleDateString('ja-JP', {36 weekday: 'short',37 month: 'short',38 day: 'numeric',39 hour: 'numeric',40 minute: 'numeric',41 })4243 // Get attendees and map to Slack user IDs44 const guests = event45 .getGuestList(true)46 .map((guest) => guest.getEmail())47 .filter((email) => email.includes('@example.com'))4849 const mentions = guests50 .map((email) => {51 const member = memberList.find((m) => m.user_name === email)52 return member ? `<@${member.user_id}>` : null53 })54 .filter(Boolean)55 .join(' ')5657 // Build event summary58 eventDetails.push(59 `\n\n${mentions}\n${title}\n${formattedStart} 〜 ${formattedEnd}`,60 )61 }62 }6364 // Prepare Slack message payload65 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 }8687 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 }78 UrlFetchApp.fetch(WEBHOOK_URL, options)9}
Setting Up the Trigger
To run this automatically every Monday morning:
- Open your script in Google Apps Script editor
- Go to Triggers (clock icon on the left)
- Click Add Trigger
- Set:
- Function:
sendWeeklyCalendarReminder
- Event source: Time-driven
- Type: Week timer
- Day: Monday
- Time: 8am to 9am (or whenever you prefer)
- Function:
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 Settings → Service 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 Settings → Script 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.