anubha — zsh — jsp-mvp
$whoami
apm, 10 months in · never deployed anything · afraid of code for a long time
$cat PROJECT.md
JSP — Job Search Platform. Built May 14 – Jun 3, 2026. Solo.

JSP

A job search platform I built from scratch to run my own search like a sales pipeline — find recruiters, get an AI-drafted email, track every follow-up, discover jobs before they saturate. Twenty days, zero prior Next.js or Supabase experience.

Timeline20 days
RoleSole builder — product, architecture, and code
StackNext.js 14 · Supabase · GPT-4o · Hunter.io
StatusLive — jsp-mvp.vercel.app
01 The problem

Job searching was disorganized across three broken pieces — finding roles, tracking outreach, and surfacing unposted jobs.

02 What I did

I built JSP solo in 20 days — three layers for outreach, tracking, and discovery — coding with AI assistance.

03 The outcome

It's live at jsp-mvp.vercel.app, with real GPT-4o email drafts, a Kanban follow-up engine, and Google auth.

The problem

Job searching breaks in three places

The idea came from personal frustration — I was targeting AI PM roles and found the entire process exhausting and disorganised. Most tools help you find a job. None of them help you run a systematic outreach campaign and track it like a pipeline — which is exactly what a serious search requires, especially for roles that never get posted.

01 · VISIBLE MARKET

Finding relevant opportunities

Naukri, LinkedIn, Indeed show you everything or nothing. No system learns what you actually want.

02 · PROCESS

Managing outreach and follow-up

Once you've found something, tracking who you contacted and when to follow up becomes unstructured fast. Opportunities die from poor tracking, not lack of ability.

03 · HIDDEN MARKET

Roles that never get posted

A large share of roles fill through referrals and direct outreach. Most job seekers only ever play in the visible market.

Architecture

Three layers, one closed loop

Mapped out from day one as three independent layers. A rule I held throughout: work on one branch at a time, and don't design the next one while building the current one.

01

Outreach

Find recruiters → generate a personalised email → send

→
02

Traction

Track every outreach → follow up on time → never lose a lead

→
03

Opportunity

Discover postings → feed into Layers 1 and 2

Hidden roles go straight into Layer 1 as cold outreach. Posted roles run the full loop: discover → reach out → track. A single lead_type field on one table handles both.

Build log

Twenty days, five milestones

Not a full commit history — the five points where the product actually changed shape.

DAY 1

$git commit -m "initial commit"

One page, a hardcoded list of fake recruiters, a static email template. Nothing real yet — just enough to prove the idea worked visually.

DAY 9

$git commit -m "migrate to TypeScript, App Router, Tailwind + Hunter.io"

Rebuilt the codebase properly — commit to the right stack rather than patch the wrong one. This is also where #0f0f0f / #4ade80 — the palette this whole page is built in — got locked in and never changed again.

DAY 14

$git commit -m "Branch 1 complete: gpt-4o wired, editable email, followup fields"

The most important single commit in the project. Real GPT-4o replaced the template. The email became an editable <textarea> — nobody should send an AI draft without reading it first. And sent_at / followup_due_at got written on save — the foundation the entire follow-up engine in Branch 2 would depend on. [NEEDS INPUT: one sentence on why GPT-4o specifically — cost, latency, quality vs. alternatives considered]. The first drafts [NEEDS INPUT: concrete example — generic-sounding, hallucinated recruiter details, wrong tone] — refining the prompt against real recruiter examples got it to a draft worth sending, not just one that existed.

DAY 14

$git commit -m "Branch 2: Kanban pipeline, drag-and-drop, follow-up engine"

The most complex build in the project, done in one session — six components, three API routes, drag-and-drop, a slide-in drawer, and a follow-up state machine: sent → reminded (day 3) → escalated (day 5) → cold (day 7), clearing instantly on any reply.

DAY 19–20

$git commit -m "Branch 3: opportunity discovery, scoring, preferences" + "google auth"

The job feed — three sources merged, scored, and ranked — landed first. Auth came last, on purpose: with zero real users yet, adding it after the product worked meant zero migration risk and a clean implementation.

What it does

The three layers, live

Layer 1 — Outreach

Tell it who you are. It finds the recruiter and writes the email.

A 14-field form — background, target role, achievements, tone — feeds both the recruiter search and the email personalisation. When Hunter.io has no data for a company (common for Indian startups), a labelled fallback pool keeps the flow from dead-ending.

jsp-mvp.vercel.app/
JSP outreach input form
jsp-mvp.vercel.app/ — recruiter leads
JSP recruiter leads with confidence scores
jsp-mvp.vercel.app/ — draft
JSP AI-generated outreach email draft
Layer 2 — Traction

Every lead lives on a board, not in your memory.

Six columns — Draft, Sent, Follow-up Due, Followed Up, Replied, Cold/Closed. Job search is a pipeline that moves over days and weeks; a Kanban makes the state of it readable in five seconds instead of a spreadsheet you have to actually read.

jsp-mvp.vercel.app/pipeline
JSP pipeline Kanban board
Layer 3 — Opportunity

A scored feed, not another job board to babysit.

Preferences set once, three sources merged into one ranked list — source is metadata nobody needs to see. Every card can go straight into the pipeline or straight into a recruiter search, closing the loop back into Layer 1.

[NEEDS INPUT: describe how the ranked feed's scoring actually works today — the real weighting/criteria behind the ranking — and any validation step, even something as simple as "manually checked top-20 vs. bottom-20 scored jobs against my own judgment of fit."]

jsp-mvp.vercel.app/opportunities
JSP opportunity preferences modal
jsp-mvp.vercel.app/opportunities
JSP scored opportunity feed

Problems faced

Three things that broke, for real

BUGHunter.io's database skews heavily US/EU. For Indian startups — CRED, Zepto, Razorpay — it frequently returned zero results, silently killing the app's core value for the companies users actually cared about.
FIXBuilt a mock pool of 20 Indian tech companies with realistic recruiter data as a labelled fallback — never pretending pattern-matched contacts are verified ones. Long-term fix identified but deferred: Apollo.io or the Greenhouse/Lever public ATS APIs, once there's traction to justify the cost.
BUGThe Kanban board's six columns rendered empty on first load, before Supabase data arrived. Technically it was working — just loading. But a blank pipeline reads as "this is broken," not "this is loading."
FIXAdded a loading skeleton — six pulsing placeholder cards while the fetch completes — plus a realistic mock dataset covering every pipeline stage, so the board always looked like a populated, working product even before real data existed.
BUGAfter adding Google OAuth, sessions didn't persist between pages — logged in on Outreach, unauthenticated on Pipeline. The standard Supabase client stores sessions in localStorage, which the server can't read during rendering.
FIXSwapped to @supabase/ssr for cookie-based sessions, readable on both client and server, plus middleware to redirect unauthenticated requests to /login.

What I deliberately left out

Not missing. Decided.

Every item below was considered, discussed with myself, and cut on purpose — the discipline that kept a solo 20-day build from turning into a project that builds forever and ships never.

✕

Bulk outreach

No sending to 50 recruiters at once. Mass outreach is impersonal and risks the relationships this tool exists to build.

✕

Autonomous AI agents

No auto-sending, no follow-ups fired without a click. The user generates, reviews, edits, sends. JSP assists — it doesn't replace judgment.

✕

LinkedIn scraping

Evaluated seriously — best recruiter data lives there. Rejected: LinkedIn actively bans scrapers and sues over it. A product built on a foundation that can vanish overnight is the wrong call.

✕

Analytics, team accounts, pricing

No charts, no shared pipelines, no monetisation in v1. Prove the loop works for one person before building for many.

Honest reflection

The code wasn't the hard part

Twenty days. No prior Next.js or Supabase experience. Three product layers, authentication, and a live deployment.

The technical implementation was AI-assisted — Claude Code, with instruction files I wrote and refined for every branch. That's transparent and intentional: I'm a PM, not an engineer, and using AI for implementation while owning every product and architecture decision was the right division of labour for building this alone.

What mattered was the decision-making — what to build in this version and what to skip, whether to add auth before or after the product worked, whether to scrape LinkedIn or find a legitimate path, how to be honest with users about mock data instead of hiding it behind confident UI. Those are PM decisions. The judgment came from ten months on the job. The code came from twenty days of finally not being afraid of it.

It isn't perfect. Recruiter data is weak for Indian companies. The job feed is limited by its free sources. I named those limits instead of hiding them — in the product, and here.