Gmail and Calendar sync for a sales CRM
Two Google OAuth integrations I built with encrypted tokens, background workers and a daily Go reconciliation job.
My role: Sole author. I built both OAuth integrations and the sync function. The surrounding CRM was co-built with a three-person team.
October 2025 to July 2026
- C#/.NET 8
- Go
- Google OAuth
- GCP Cloud Scheduler
Why the CRM needs the inbox
A card on the pipeline board represents a deal, but a lot of its real activity happens somewhere else: a quote sent by email, a reply from the customer, or a site visit added to a calendar.
Asking the sales team to copy all of this into the CRM by hand does not work for long. The board slowly becomes outdated, and people stop trusting what they see inside it.
To avoid that, the CRM synchronizes Gmail and Google Calendar directly. This also means storing long-lived Google refresh tokens, which give access to a person’s inbox and need to be handled carefully.
What I built
I built both integrations inside the .NET 8 API used by the fleet operations console. The surrounding CRM was co-built by our three-person team, but the Google integrations are mine from the OAuth flow to the background sync.
Gmail, about 1,200 lines, with a roughly 500-line service at the centre of it. It manages the OAuth flow and token refresh, retrieves new messages, then attaches each relevant email to the correct case.
Google Calendar, about 1,400 insertions, syncing events and their attendees and linking them back to pipeline cases. A visit in the calendar and the event shown on a card come from the same data instead of becoming two separate records.
All access and refresh tokens are AES-encrypted before being stored.
Two ways of keeping the data current
The data is synchronized in two different ways: background workers inside the .NET API, and a Go Cloud Function started once a day by GCP Cloud Scheduler.
The in-process workers are incremental and per-user. They keep emails and calendar events current while people are using the console. They run where the database context and token decryption already exist, which makes these small and frequent updates easier.
The scheduled function is a daily bulk pass. It goes through Gmail threads for the complete pipeline rather than only the cases currently being viewed. This job is longer and does not need to respond quickly, so keeping it outside the API prevents it from competing with normal requests.
Because it runs separately, the function has its own timeout and memory. If it stops, the API continues working and Cloud Scheduler can start a new run the next day.
Making failures visible
The function validates all required environment variables when it starts, every outbound request has a timeout, and non-2xx responses are returned as errors instead of being ignored. A scheduled job that reports success while doing nothing is much harder to notice than one that clearly fails.
A timezone bug
At one point, reminders in the same API were being shifted to the wrong day for
some users. A reminder stores a date, not a precise moment in time, but it was
handled like a timestamp and changed when the timezone changed. I tracked the
problem to the DateOnly handling and fixed it. This was the kind of issue that
never appeared in a test environment running in UTC.
In numbers
The Gmail integration contains roughly 1,200 lines, including a 500-line service, and the Calendar integration added around 1,400 lines. I wrote both of them and their background workers, and the daily Go reconciliation function was shipped as one complete commit.
Across the API as a whole I have 554 of 1,287 commits (43%), second only to the engineer who created the repository. The board using these integrations is written up separately.
Get in touch
I am available for remote freelance work. The easiest way to reach me is by email.