THeApps
← All work
FPheLpLive

Standings, results and the money pot for a private fantasy football league, drafted for the organizer to send.

Language
  • TypeScript
  • Node 24
Framework & UI
  • Next.js 16.3 — App Router
  • React 19.2 — React Compiler
  • Tailwind v4
Data
  • Postgres
  • Drizzle — ORM + migrations
Auth & Identity
  • Auth.js v5 — magic link
  • Allowlisted owners — no self-signup
Integrations
  • FPL API — unofficial
  • Resend — email
  • WhatsApp — share link
Hosting & Infra
  • Railway — web + cron service + Postgres
QA & Testing
  • Vitest — unit
  • Playwright — e2e
  • Playwright — screenshots
  • Recorded API fixtures
Dev Tooling
  • npm
  • ESLint
  • tsx
CI & Delivery
  • Railway — deploy on merge to main
  • Migrations on pre-deploy
  • No staging environment yet
Observability
  • Sentry — errors + cron monitors

The problem

Someone runs the league. Every week they open the fantasy site, read the table, work out who won the gameweek and by how much, remember who is owed what from a pot of a few hundred dollars, and type all of it into WhatsApp by hand. Then they do it again next week, for nine months.

It isn’t hard work. It’s just work that never stops, and the person doing it is doing it for their friends.

The approach

Automate the reading and the arithmetic, facilitate the sharing.

The app watches the league, computes the digest, and has a draft waiting when the organizer opens it. They edit it with their own update and comments, tick which blocks to include, and tap once. WhatsApp opens with the message pre-filled and they choose the group themselves. And/or email is sent to the group. The conversation and banter around it still lives there.

League members never sign in. They don’t have accounts, and the app has no contact details for them beyond what the organizer types in. Only the person doing the work needs to be a user.

The gate

The interesting engineering isn’t the standings table. It’s knowing when the standings are true.

The fantasy API marks a gameweek finished before bonus points are applied, and recalculates league tables on a schedule of its own. Both facts are invisible if you trust the flag: you get a table that looks complete, is wrong, and has already been sent to eighteen people.

On 24 August the league average read 52.82. Nothing was left to play. Fourteen hours later, with no further fixtures, it settled at 53.65. A digest sent on the earlier reading would have published wrong totals to the whole league and nobody would have known to check, because the message would have looked exactly right.

The wider lesson is about the API rather than this league. It’s first-party but undocumented, with no stability guarantee and shapes that shift between seasons. So real responses are recorded as fixtures and parsed by the real code on every test run: when the shape changes, the test suite fails instead of the send. The diff is the finding.

Status Live

Live, running a real 17-manager league through the season. The first gameweek was scored and verified end to end on 25 Aug 2026. Sign-in is allowlisted to specific addresses for now; open sign-up is the next phase.