Skip to content

Use Google Calendar API calendar invites / Implement Push Notifications#1529

Open
davinotdavid wants to merge 39 commits intomainfrom
google-ics-cards
Open

Use Google Calendar API calendar invites / Implement Push Notifications#1529
davinotdavid wants to merge 39 commits intomainfrom
google-ics-cards

Conversation

@davinotdavid
Copy link
Copy Markdown
Contributor

@davinotdavid davinotdavid commented Feb 25, 2026

Description of the Change

TL;DR: If the default calendar (the one selected in the Availability) is a calendar attached to a Google Account, we are now using Google Calendar's email flow instead of our own. We retrofit the event decisions into Appointment through Push Notifications into a webhook.

Shouldn't be super dangerous as there's an env var (GOOGLE_INVITE_ENABLED) that functions as a feature flag that is False in prod and True in non-prod environments.

PS: Sorry for the super long PR, lots of cases to cover

--

Whenever a Subscriber has a google account / google calendar as their default calendar, we are passing the control of the invitation emails to Google through their Events API. This is done through using insert instead of import while adding the event to the remote calendar.

Not having our own invitation emails means that we lose the visibility on the Confirm / Deny / Cancel options so in order to solve for it, this PR implements their Push Notifications workflow that work as follows:

sequenceDiagram
    participant Bookee
    participant App
    participant GoogleCal as Google Calendar
    participant Subscriber

    Bookee->>App: Request time slot
    App->>GoogleCal: "insert() with bookee as attendee, status=tentative"
    GoogleCal-->>Bookee: Google Calendar invite (tentative)
    App-->>Subscriber: Branded confirmation email

    alt Subscriber confirms (email/UI)
        Subscriber->>App: Accept
        App->>GoogleCal: "patch() status=confirmed, sendUpdates=all"
        GoogleCal-->>Bookee: Update notification (confirmed)
        App->>App: Book slot, update DB status
    else Subscriber denies (email/UI)
        Subscriber->>App: Deny
        App->>GoogleCal: "delete() sendUpdates=all"
        GoogleCal-->>Bookee: Cancellation email
        App->>App: Mark slot declined
    else Bookee declines (Google Calendar)
        Bookee->>GoogleCal: Decline invite
        GoogleCal-->>Subscriber: Decline notification email
        GoogleCal->>App: Watch webhook
        App->>App: Mark slot declined, delete event
    else Bookee accepts (Google Calendar)
        Bookee->>GoogleCal: Accept invite
        GoogleCal->>App: Watch webhook
        Note over App: No action - subscriber must still confirm
    end
Loading

The above is a partial graph but consider that the Subscriber's actions through the Google Calendar Event would also backtrack to our application! Also, there's no HOLD event for this flow anymore.

Here's some specific use-cases to consider:

  • FTUE use-case

For users that opt to connect a google account / google calendar we now create an entry in the google_calendar_channels table so that we have a sync token to compare with when the webhook is triggered with updates.

  • Existing users that have a Google Account / Calendar as the default calendar (post-FTUE)

Added a one-off command that can be ran to back-fill the watch channels required for the push notifications to work if the user has the default calendar as a Google Calendar and it is currently connected.

  • Existing users that have a Google Account / Calendar but it is currently disconnected in Settings and not default (post-FTUE)

When a calendar is connected through the Settings, if the external connection is a google connection, we are still not creating the watch channel. We only do so if the calendar is set as default. Likewise, if the calendar is not the default for a schedule anymore, we teardown the watch channel.

  • Expired sync tokens

There is a command to renew the sync tokens for the channels that are in the google_calendar_channels table. However, we don't have a task queue setup yet for this so this has to be ran manually for now.

How to test

Note

Unfortunately, the Push Notifications API / Google Webhook require a publicly available URL with HTTPS. The most seamlessly way that I could find was to use Cloudflare Tunnels pointing to a personal domain that I already own and routing that to localhost:5000.

  1. Assuming that you already have a publicly exposed HTTPS URL, take down all your containers and bring them up again docker compose down -v && docker compose up -d --build and before you do anything else, update the get_webhook_url's backend_url to be the publicly exposed HTTPS URL (backend/src/appointment/controller/google_watch.py).
  2. Go through the FTUE and connect a Google Account / Google Calendar
  3. Copy & open your Booking Page URL and book any events
  4. Check that the Subscriber receives our own branded email
  5. Check that the Bookee receives the Google Calendar invite email
  6. As a Subscriber, go to Google Calendar and accept / decline the invite
  7. As a Bookee, check that you've received the Google Calendar email notification about the change of status of the event
  8. Go to the Bookings page and check that the status of the event matches what has been answered

Some other cases to test:

  • "Automatically confirm bookings if time is available" On / Off should behave as expected
  • Declining the Google Calendar invite from the Bookee perspective through the Google Calendar UI / email RSVP should also decline / cancel the event for the Subscriber

Benefits

  • For Google Account users, this will reduce the amount of no shows since we would be able to reliably insert events into users' Google Calendars. The original problem was that since we were silently adding events through the import method (which doesn't send Google emails) instead of insert (which sends Google emails) and using our own emails for notification, unless the recipient had already interacted with no-reply@appointment.tb.pro or manually clicked in the "Add to calendar" button in their email, they wouldn't have the event added to their calendar automatically.

Known issues / Things to improve

  • There are more actions that can be done through the Google Calendar API that are currently not mapped to features we have in Appointment like the 'Maybe' option or the 'Change time' / 'Propose new time'.
  • We need to renew the watch channels periodically but we don't have yet a task queue setup. We had a brief discussion on using Celery but instead of having a new infrastructure for it inside Appointment, we can explore re-using the existing on in Accounts (TBD though). To mitigate this I can manually add to my calendar to run the commands :/

Applicable Issues

Fixes #1393

@davinotdavid davinotdavid changed the title [WIP] Use Google Calendar API calendar invites / Implement Push Notifications Use Google Calendar API calendar invites / Implement Push Notifications Mar 10, 2026
@davinotdavid davinotdavid marked this pull request as ready for review March 10, 2026 23:12
@davinotdavid davinotdavid requested review from devmount and removed request for devmount March 10, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Confirmed Events not showing up on other person's calendar

2 participants