Skip to content
Resume (PDF)

Job Matcher

A self-hosted tool that watches the job boards I follow and scores every new listing against my resume. I use it in my own job hunt.

Role
Sole developer
When
October 2026
Stack
TypeScript, Bun, NestJS, Next.js, Effect, PostgreSQL, Prisma, BullMQ, Docker, Playwright

The problem

Job hunting meant checking many careers pages and job boards by hand, then reading every listing to judge whether it fitted. That didn’t scale.

What I did

Job Matcher re-checks the boards and careers pages I follow on a schedule, drops listings outside my locations, experience level and title keywords before spending anything on AI, then has an LLM score each remaining job against my resume and explain the fit.

  • Typed failures. The matching pipeline is written with Effect, so every failure is a typed error and each one is handled on purpose: a rejected API key stops the run, a blocked job page fails only that job, and a Discord outage is only logged.
  • Safe, idempotent runs. The API only queues a run and a BullMQ worker does the slow work. The run id doubles as the queue job id, so a double click can’t start a run twice, and a per-user Postgres advisory lock stops runs overlapping.
  • Budget-aware by design. Each run reads the scraping plan’s remaining credits and keeps a reserve, scrapes on a self-hosted instance first and falls back to the cloud only for pages that block it, and only uses an LLM endpoint under a per-token price cap.
  • Resumable. Scraped pages are cached in Postgres and evaluations are keyed by a hash of the resume, sources, model and requirements, so a rerun picks up where the last one stopped.
  • Tested like a product: 50+ test files with a 90% per-file line-coverage gate, Playwright end-to-end tests against fake providers, and CI that boots the full stack and runs the installer before anything merges. It installs with one command on Windows, macOS and Linux.
Job Matcher's Analyze page: a completed run that found three listings, checked two, and marked both as matches, each with a one-line reason.
A completed run: listings found, checked and matched, with the reason for each. Demo data.
Job Matcher's Matches page listing two pending matches, each with a Mark applied button.
Matches wait here until I mark them as applied. Demo data.

Outcome

I use it regularly in my own job hunt. It saves me screening every listing by hand, and it has surfaced roles I went on to apply for.