Note

The project page is the tech spec & formal guide. This post is the build story.

The PGA Championship wrapped with a botched draft. Nobody was to blame. Six guys across three timezones, a group SMS, Discord channels, and a recently retired commissioner—we had become victims of our own creation. The process worked until it didn’t, and we had never actually written it down.

I had been looking for a hobby project to run through a coding agent: a new codebase, not the Home Assistant tangle I already live in (flagged for a future post). More than that, I wanted to play product manager, not developer. Dictate the requirements. Evaluate the tradeoffs. Stay out of the latest frontend changelog and vendor API spec.

Those two problems clicked on a dog walk. I strolled with my senior Golden Retriever (he may have also laid down) with Claude and started from the process we already used: mostly manual, mostly inferred, never quite specified. The Open Championship was a few weeks out. That became the ship date. What follows is how those requirements actually came to life.

Gathering requirements

For several years, we had been getting by with a combination of SMS group threads and Discord banter. We generally do a pool for all the major tournaments (Masters, PGA Championship, US Open, and The Open Championship) in the same format every time:

  • 2 Exclusive Player Picks per participant chosen via randomized snake draft
  • Scoring best ball, with double CUT penalty ($9) & tournament winner bonus ($5)

ESPN generously (unknowingly? probably not…) lends its APIs to the public with no documented SLA and no developer registration required. For my needs, this was the right combination of trusted source and path of least resistance. With that, I had a general idea of my architecture and requirements. Seems straightforward enough, right? Well underlying all of this is a lot of nuance and human inference that isn’t straightforwardly deterministic.

Okay, but really gathering requirements

The two key processes (draft & live scoring) were the actual specification. A live draft needed low latency, transparent draft orders and a live on-the-clock timer ⏲️ helps urge speed without stealing or skipping a pick.

Once a tournament was live, scoring was no less nuanced: standings would JOIN our draft picks to live scores, but needed to handle ties & tiebreakers, withdrawn players (or the even less likely DQ), and the CUT penalties when they applied. Members shouldn’t need to check a live scoreboard to confirm their standing on the pool, which could happen dozens of times throughout a live round.

Needless to say, it quickly dawned on me that I would need a much beefier stack than my static blog could handle (🩵 netlify, fwiw). The live draft requires shared writes. Scoring needs continuous polling after tabs close. This was a bigger bite than I had anticipated.

I settled on PostgreSQL for the backend (fine CRUD, easily scales 100x my scale). To host a single pool with friends, I targeted deployment to my homelab: one dedicated Docker container, zero extra cost, and with ngrok I could serve the app (and Discord) securely. No half-dozen hobby-tier trials for compute, database and hosting services. This was the accelerator.

Requirements delivered

Our first pool was ready for draft night, and everything went pretty smoothly! Funny enough, my many hours testing were probably higher volume than our production workload. 😅

Nevertheless, the draft ran flawlessly, technically speaking. There were some slow picks, inevitably. Nobody accidentally picked the wrong Fitzpatrick. Live standings showed everyone where they sat. Members called them noice and elite. The trash talk flew like it always had, but we still felt a little disconnected—until Discord landed for the second event, with slash commands and a live draft.

The bar was truly raised in the next tournament. I was blown away by how seamlessly the event unfolded in the server: a dedicated thread for the draft, and live scoring digests on demand.

Discord bot help slash-command lists available commands and decodes emoji shorthand, with link to full user guide.

Discord became the primary surface for the second tournament—slash commands so people could stay in the banter.

Merely automating our manual process wasn’t enough to build momentum. The magic happened when Discord began to natively support the draft and live scoring. Banter topped prior records, engagement unraveled frictionlessly for everyone involved. Even non-members of the pool could lurk and watch storylines develop.

Reflections on Cosplaying PM

My day job leans heavily on demos: show the value, sell the vision, then move on. If I’d stopped when the Golf Pool demos looked good, I’d have shipped a genuine support nightmare. One botched final standings and I’d send us back to group SMS forever. Owning that is what turned this from a vibe-coded prototype to something I’d put my name on.

The harder part was not jumping the roadmap and trying to push the next shiny feature mid-season.

Fighting scope creep was my biggest challenge. My mental roadmap (wishlist) still has a pile of engagement-multiplier stretch features: LLM-powered trash talk, a custom menu of optional bonuses, or side betting a hedging prediction market.1

Those will all have to wait until next season and next release, when perhaps the Golf Pool Bot graduates to generally available.


If you want more details on features and a mock demo, they live on the Golf Pool project page.


  1. My homelab isn’t ready for large language models, but I envision a trash-talking element that pulls from famous golf cinema like Happy Gilmore and Caddy Shack. For new penalties/bonuses, especially love the option to punish the first pick overall if they come in last, or use total time-on-clock as a ruthless tiebreaker. And of course we need ways to hedge our investments, this is 2026, people! ↩︎