Spite is an underrated product requirement
I like Calendly. I genuinely do. It solved a real problem for a long time. But at some point my setup started feeling cramped enough that I hit my favorite engineering trigger: respectful annoyance.
The actual problem was not that I needed some massive scheduling platform. I needed a few different booking flows for a few different contexts: interviews, networking chats, mentoring calls, and the occasional “let’s just find time” link. On the free plan, I effectively had one active event type to work with. So I would switch that one time block around depending on what I needed most at the time.
That worked for a little while. Then it got annoying. If I was focused on interviews, I would make the interview block active. If someone needed a quick networking chat, I would change it again. If I wanted to open up mentoring time, same thing. It was not technically hard, but it was the kind of tiny repeated friction that starts to feel ridiculous when you know you can probably build your way around it.
And honestly, the deeper truth is less noble: I did not want to pay. Calendly was not doing anything evil to me. Their product has pricing tiers, like every other SaaS product. But once the free tier stopped matching how I wanted to use scheduling, I had two choices. Pay for convenience or sacrifice an evening to avoid paying for convenience forever.
Naturally, I sacrificed the evening.
Situation
My scheduling needs were simple, but not identical. Interviews needed stricter windows. Networking calls could be looser. Mentoring calls needed their own rhythm. Trying to squeeze all of that into one active booking type meant I was constantly changing the same block to fit whatever mattered that week.
That is the kind of workflow that feels harmless until it becomes part of your routine. Open Calendly, adjust the event, rename it, change the availability, send the link, then remember later that the same link is now doing something else. The software was not broken. My use case had just outgrown the free version.
Task
I did not want to rebuild all of Calendly. That would be absurd. I wanted one thing: a reliable booking flow for my personal site and chatbot, grounded in my actual Google Calendar availability, with rules I controlled.
The requirements were pretty clear:
- Use my real calendar availability instead of fake static slots.
- Let me define interview friendly windows.
- Prevent last minute chaos with minimum notice rules.
- Handle OAuth failures cleanly.
- Keep the user experience simple: email, pick time, done.
In other words, I needed just enough scheduler to solve my problem, not enough scheduler to accidentally become a full time scheduling company.
Action
I built a small scheduling layer in SvelteKit with a few API routes and one auth helper:
/api/calendar/availability: returns open slots./api/calendar/book: creates the event after a second availability check./api/calendar/oauth/startand/api/calendar/oauth/callback: reconnect flow when OAuth refresh token goes stale.google-calendar-auth.ts: manages OAuth config and refresh token persistence.
The front end is intentionally boring, which I mean as a compliment. Enter email, click a slot, send invite. Nobody booking time with me needs a design manifesto. They just need the button to work.
Token persistence, because auth is where confidence goes to die
The hardest part was not slot math. It was auth durability.
I persist the Google refresh token in Postgres:
- Create the table if it does not exist:
google_oauth_tokens. - Read the token from the database first, then environment variables second.
- If Google rotates a refresh token in a token response, store the new one.
- If
invalid_granthits, clear the stale token and force the reconnect flow.
That was the part that made the tool feel real. I did not want a scheduler that worked only while the env var gods were in a good mood. I wanted something that could fail clearly and recover without turning into an 11pm debugging session.
Slot generation logic
I intentionally constrained the booking window to keep interviews predictable:
- Timezone: America/Los_Angeles
- Weekdays only
- 8:00am to 2:00pm PT window
- 30 minute slots
- 24 hour minimum notice
- Lookahead capped to the next 5 working days
The availability endpoint generates candidate slots first, then calls Google FreeBusy and removes overlaps. So the user only sees times that survive both my rules and my actual calendar conflicts.
Defensive booking flow
The booking endpoint validates again at request time:
- Checks email format.
- Rechecks notice and working window rules.
- Runs FreeBusy again for race conditions.
- Inserts the event with both attendees and sends updates.
That second FreeBusy check matters. Without it, two fast clicks can produce one awkward apology email. I do not need my scheduler creating social debt. I can do that manually.
Result
The result is a booking flow that matches how I actually work. It is not as polished as a mature SaaS product, but it does exactly what I need. It lives on my site, respects my calendar, follows my rules, and does not ask me for ten dollars a month just because I have more than one kind of conversation.
Was it financially rational to spend an evening building something that would have cost less than a lunch every month? Probably not if you only count the hours. But if you count ownership, learning, and the quiet satisfaction of never having to toggle that one active time block again, it was absolutely worth it.
What I like about this approach
- I own the constraints and UX.
- I can tune behavior per use case.
- I am not dependent on pricing tiers for basic routing logic.
- Failures are explicit and recoverable.
What I gave up
- I now own auth edge cases and operational drift.
- I lost some polished SaaS convenience out of the box.
- I have to keep this code healthy as APIs evolve.
Worth it for me, because control and reliability for this specific flow mattered more than feature breadth.
The dangerous little product thought
The funny part is that once you build the thing for yourself, the intrusive founder thought shows up: should I just build a lightweight scheduling tool that charges half?
I am only half joking. Scheduling itself is a product category, not one company’s private concept. A cheaper tool with original branding, original code, and a narrower feature set could be a real product if it solved a specific pain well. The important part would be not pretending to be Calendly, not copying their interface, not using their marks, and not positioning it as anything affiliated with them. Just a small, honest scheduler for people who want the boring parts to work and do not need enterprise everything.
Also, charging half sounds easier when the only employee is me. No sales team. No support department. No investor update. Just one guy, a calendar API, and the questionable confidence that every “simple” product stays simple if you stare at it hard enough.
Final thought
This whole project started because I got tired of changing one time block over and over. That is not exactly a heroic origin story, but a lot of useful software starts that way. Some repeated annoyance becomes visible, you realize the workaround is now part of your life, and eventually you either pay for the upgrade or build the escape hatch.
This time, I built the escape hatch. Spite driven development, but make it production safe.