Skip to content
asim.dev
all work
personal productDesign, architecture, full build · 2026

Job Radar

A job-hunting engine that works while I sleep

Every ~3 hours a scheduled tick runs a serverless search across JSearch and three remote feeds; results are skill-matched, deduplicated into SQLite, and emailed as a digest — while the dashboard tracks status, keywords, and AI-tailored résumés.

  • Next.js
  • TypeScript
  • Drizzle ORM
  • Turso / SQLite
  • Gemini API
  • Nodemailer
  • cron-job.org
Job Radar dashboard — scored job feed with keyword manager and activity log

01 / context

Job hunting at the junior-to-mid level is a filtering problem: hundreds of postings across a dozen platforms, most of which don't fit. Scanning them manually is a full-time job nobody pays for. I wanted the funnel inverted — roles that match my stack should arrive already ranked, deduplicated, and readable in one email.

Job Radar is that system. It runs itself: an external cron ticks every 30 minutes, an in-app cooldown turns that into a real Next.js serverless search every ~3 hours (self-healing missed ticks), pulling from JSearch and three free remote feeds in parallel, and deciding what's worth my attention.

02 / problem

Three constraints shaped the design. It had to run on free-tier infrastructure (Vercel Hobby, Turso's free SQLite, Gmail SMTP). It had to be stateful enough to dedupe across runs and track application status. And matching had to be tunable without redeploying — keywords live in the database, edited from the dashboard.

03 / decisions

The pipeline is one serverless function with clear stages

app/api/search/route.ts
// 1. read keywords from DB        3. filter by skill match
// 2. fetch JSearch + RemoteOK      4. dedupe against stored jobs
//                                 5. persist + email the digest

Each stage writes a search log, so the activity view can answer why a run found 3 jobs when yesterday's found 40. Observable-by-default beats clever.

Drizzle + Turso for typed SQL on free tier

Three tables — jobs, search_keywords, search_logs — with Drizzle providing the typed query layer. Schema-as-code means migrations are reviewable PRs, and Turso's free tier comfortably handles cron-sized writes.

AI résumé tailoring with a human in the loop

Pick a saved job — or paste any job description — and Gemini returns a structured proposal: a relevance score, targeted edits to headline, summary, skills and experience bullets (each with a before/after and a JD-driven why), plus a draft cover letter. The guardrails are the point: job titles, dates, contact and education are immutable in the type system, and the DOCX renderer owns all formatting — the model can only propose wording, never invent history.

Every proposal is reviewed edit-by-edit — accept, reject, or tweak — before a tailored .docx is generated server-side and saved to a history of variants. It turned the system from a reader into a small apply-workflow.

04 / outcome

  • Fully automated on free-tier infra — zero running cost, zero maintenance windows.
  • Deduplication across sources and runs — each posting appears exactly once, ever.
  • Skill-match filtering tuned live from the dashboard's keyword manager.
  • Email digests with direct apply links and relevance score.
  • AI résumé tailoring that stays honest — Gemini proposes, I review, the renderer enforces the truth.

05 / what i'd do differently

I'd add match-quality telemetry from the start — logging which keywords actually led to applications would make tuning objective instead of vibes-based. And the matcher's current scoring is a weighted keyword count; an embeddings pass on job descriptions is the obvious next upgrade, cheap to add behind the same interface.