Reactivating Boatsetter Calendar Sync: Resolving OAuth Expiry and Missing Trigger Setup in Google Apps Script
The Boatsetter booking integration was fully implemented but silently broken in production — the iCal feed was wired into CalendarSync.gs, but the automation trigger was never activated, and OAuth tokens had expired. This post walks through the diagnosis, the three-step fix sequence, and why this failure mode is worth documenting for future integrations.
The Problem: Three Interdependent Blockers
Ticket m-91325edb was marked "needs-you" because the Boatsetter sync required manual intervention at multiple layers:
- Missing trigger setup:
calendarSyncSetup()inCalendarSync.gshad never been executed, so the time-based triggers that should poll Boatsetter's iCal feed every 30 minutes were never created. - Expired Gmail OAuth: Ticket t-8d86d5ba indicated the OAuth token used by GAS for Gmail scope had expired, preventing the script from sending reconciliation emails even if sync data existed.
- Revoked Calendar OAuth: The Calendar API write permission had been revoked (likely during a security audit or account maintenance), blocking all
CalendarAppcalls that insert Boatsetter events.
Any one of these would fail the sync; all three being present meant the integration was completely non-functional despite being code-complete.
Step 1: Re-authorizing GAS and Re-consenting OAuth Scopes
Google Apps Script maintains OAuth consent at the project level, not the individual script. When you execute any function in the GAS editor that touches a protected API (Gmail, Calendar, Sheets, etc.), the first execution triggers Google's consent dialog.
Why this works: The GAS runtime binds the script execution to your Google account and prompts re-consent for any scopes the script declares. This is distinct from standalone OAuth tokens — it's part of GAS's built-in OAuth flow.
Implementation steps:
- Navigate to the CalendarSync.gs project:
https://script.google.com/d/1HiEgjBrCGrnOIvr27nIk1E1qoR1pwKmWLigl191Tz0xq7LautJrIp9Ii/edit - In the function dropdown menu (top center), select
testSync— a lightweight read-only function that validates both Gmail and Calendar scopes without mutating data. - Click Run
- Google displays an authorization prompt: "This app needs access to your Google account. Review the permissions requested."
- Click Allow for the first scope (Gmail: send emails, read draft metadata).
- A second prompt may appear for Calendar scope (read/write calendar events). Click Allow again.
- Check the Execution Log (bottom of editor) for any errors. You should see clean output with no permission exceptions.
This single operation re-establishes trust between the GAS project and your Google account for both Gmail and Calendar APIs, clearing both t-8d86d5ba and the revoked Calendar OAuth issue.
Step 2: Activating Time-Based Triggers via calendarSyncSetup()
The ticket originally referenced calendarDashboardSetup(), but the actual function name in CalendarSync.gs is calendarSyncSetup(). Both exist in the Jada ecosystem, but they serve different purposes — the naming matters because running the wrong one won't create the Boatsetter sync triggers.
What calendarSyncSetup() does:
// Pseudo-code structure (actual implementation in CalendarSync.gs)
function calendarSyncSetup() {
// Delete any existing triggers to avoid duplicates
deleteExistingTriggers_();
// Create time-based trigger: syncAllChannels every 30 minutes
ScriptApp.newTrigger('syncAllChannels')
.timeBased()
.everyMinutes(30)
.create();
// Create daily trigger: sendDailyReconciliation at 7:30am PT
ScriptApp.newTrigger('sendDailyReconciliation')
.timeBased()
.atHour(7)
.atMinute(30)
.onWeekDay(ScriptApp.WeekDay.MONDAY, ScriptApp.WeekDay.TUESDAY, ...)
.create();
}
Why idempotency matters: If you run calendarSyncSetup() multiple times, you'll create duplicate triggers. The function handles this by deleting existing triggers before creating new ones, but it's still good to verify.
Activation steps:
- In the same GAS editor, function dropdown → select
calendarSyncSetup - Click Run
- Watch the Execution Log for completion (should be instantaneous)
- Verify trigger creation by clicking the clock icon (⏰) in the left sidebar under Triggers
- You should see two new entries:
syncAllChannels— Time-based, every 30 minutessendDailyReconciliation— Time-based, daily at 7:30 AM (America/Los_Angeles)
These triggers now run automatically without further intervention. The Boatsetter iCal feed will be polled every 30 minutes via the syncAllChannels function, which internally calls the Boatsetter-specific sync logic.
Step 3: Verification and Validation
After both OAuth re-consent and trigger activation, validate the entire pipeline:
- Function dropdown → select
testSync - Click Run
- Examine the Execution Log for the following successful output pattern:
Fetching Boatsetter iCal...
[N] events from Boatsetter
CalendarSync complete. New bookings: [M]
Updating calendar entries...
✓ Calendar events synchronized
✓ Reconciliation email sent
What each line means:
Fetching Boatsetter iCal...— The script successfully retrieved the iCal feed from Boatsetter's public URL (hardcoded inCalendarSync.gs). No network errors or 403/404 responses.[N] events from Boatsetter— Parsed N events from the iCal format. If 0, either Boatsetter has no upcoming bookings or the feed URL is stale.New bookings: [M]— M of those N events were new (not previously in the Jada calendar). This is the reconciliation count.Calendar events synchronized— The script successfully wrote to Google Calendar API. No permission errors (which would show asException: You do not have permission to perform this action).Reconciliation email sent— The Gmail scope