```html

Fixing Calendar Sync Failures: Reauthorizing GAS, Activating Triggers, and Validating the Boatsetter Integration

What Was Done

The Boatsetter calendar integration had been deployed but was never operationalized. Three blockers prevented auto-sync from running:

  • OAuth tokens for Gmail and Google Calendar had expired or been revoked at the GAS (Google Apps Script) level
  • The time-based triggers that execute the sync and reconciliation processes were never activated
  • The integration code existed but had never been validated end-to-end in a live execution

This post walks through the three-step fix: reauthorizing GAS permissions, activating the trigger functions, and validating the full pipeline with test execution logs.

Technical Details: The Three Blockers

Blocker 1: Expired OAuth Tokens

GAS projects maintain their own OAuth scope permissions separate from the Python Lambda tokens discussed in earlier infrastructure work. When a GAS function attempts to call Calendar.getCalendarById() or GmailApp.sendEmail(), it needs explicit user consent to those scopes.

The original setup had granted these scopes, but they had either expired or been revoked (common when testing OAuth flows or when Google's automatic token refresh fails). The fix is not to re-issue new tokens programmatically, but to trigger the native Google consent dialog by running any function that requires those scopes.

Why this approach: GAS's OAuth model ties permissions to the user who last ran the script in the editor. Running a function that touches Calendar or Gmail APIs causes the browser to prompt for consent, and once granted, the tokens are valid for an extended period (typically until manually revoked).

Blocker 2: Triggers Never Activated

Even with valid OAuth tokens, the scheduled sync and reconciliation won't run unless time-based triggers are installed in the GAS project. The trigger setup is idempotent—it checks for existing triggers before creating duplicates—but it must be called at least once.

The setup function creates two triggers:

  • syncAllChannels: Executes every 30 minutes to fetch new bookings from all iCal feeds (Boatsetter, Airbnb, Viator, etc.)
  • sendDailyReconciliation: Executes daily at 7:30am PT to email a summary of new bookings to ops

Blocker 3: Never Validated End-to-End

The iCal URL for Boatsetter was wired into the ICAL_FEEDS array, but the full pipeline (fetch → parse → write to calendar → send email) had never actually executed in production. A test run catches issues like malformed iCal, missing calendar IDs, or email delivery failures early.

The Fix Sequence

Step 1: Open GAS Editor and Trigger OAuth Consent

Open the GAS editor for the CalendarSync project:

https://script.google.com/d/1HiEgjBrCGrnOIvr27nIk1E1qoR1pwKmWLigl191Tz0xq7LautJrIp9Ii/edit

In the editor:

  • Function dropdown (top left) → select testSync
  • Click the Run button
  • Browser prompts: "This app needs access to your Gmail and Calendar" → click Allow
  • If two separate prompts appear (one for Gmail, one for Calendar), allow both

File and function reference: sites/queenofsandiego.com/CalendarSync.gs, function testSync() at line 563.

The testSync function is lightweight—it calls syncAllChannels() with console logging but doesn't send emails—making it ideal for triggering consent without side effects.

Step 2: Activate the Time-Based Triggers

Once OAuth is valid, activate the trigger setup:

  • Function dropdown → select calendarSyncSetup
  • Click Run
  • Watch the Execution log for the success message

File and function reference: sites/queenofsandiego.com/CalendarSync.gs, function calendarSyncSetup() at line 355.

This function:

  • Creates or updates the BookingLedger tab in the ops Google Sheet (used for audit trails)
  • Removes any stale triggers (cleaning up duplicates from previous test runs)
  • Installs fresh time-based triggers for syncAllChannels (every 30 min) and sendDailyReconciliation (7:30am PT)

Expected log output:

CalendarSync setup complete:
  - BookingLedger tab created in ops sheet
  - syncAllChannels: every 30 minutes
  - sendDailyReconciliation: daily at 7:30am PT
  - Next step: add iCal URLs to ICAL_FEEDS array as platforms go live

Step 3: Validate the Full Pipeline

Run testSync again to verify the entire sync pipeline:

  • Function dropdown → select testSync
  • Click Run
  • Watch the Execution log for 10–20 seconds

Expected output (for Boatsetter, assuming live bookings exist):

Fetching Boatsetter iCal from: https://ical.boatsetter.com/...
  Parsed 5 events from Boatsetter iCal
Processing events: 3 new bookings, 2 updates
Writing to calendar: queen-of-san-diego@...
CalendarSync complete. New bookings: 3

Success criteria: No lines containing "Exception: You do not have permission" or "Error: 401 Unauthorized". If the log shows these, OAuth still isn't valid or the iCal URL is incorrect.

Infrastructure: GAS Project Architecture

The CalendarSync GAS project is bound to the ops Google Sheet and has the following resource dependencies:

  • Calendar: queen-of-san-diego@googlegroups.com (target calendar for all synced bookings)
  • Sheet: Ops Sheet (for BookingLedger tab and trigger logs)
  • Gmail: ops@sailjada.com (sender for daily reconciliation emails)
  • i