RISHET MEHRA · DTU '27

03 / LeadPilot · live app

A live app, not a demo.

Lead capture and enrichment you can open and use. Next.js and Postgres, queued enrichment, real deploy with no mock data.

What it does

LeadPilot is a live app for capturing leads and enriching them without losing any along the way. You open it, fill the form, and the row lands in Postgres. Enrichment runs in the background, and the dashboard counts you see are real, not seeded fixtures.

The emphasis was shipping, not mocking. No demo data, no screenshot flow that never went live. The live card on the portfolio points at the same deploy you can try.

  • Capture form that writes straight to Postgres through a Next.js route
  • Enrichment step that queues work when the provider throttles
  • Dashboard with this week count that reads from the same DB
  • Small scope, real deploy, no hidden staging copy

Limitation: enrichment is rate limited by the provider. Bulk imports queue and finish in order instead of all at once.

How it works

Next.js handles the routes, Postgres holds the truth, and a tiny queue makes enrichment resilient. The key idea is never drop a lead when the upstream says slow down.

app/api/leads/route.ts · create a leadNext.js · Postgres
// POST /api/leads · write and return the row
import { sql } from '@/lib/db'

export async function POST(req: Request) {
  const { name, email, company } = await req.json()
  const [row] = await sql`
    insert into leads (name, email, company) values
    (${name}, ${email}, ${company}) returning *
  `
  await queue.add('enrich', { leadId: row.id })
  return Response.json(row)
}
lib/queue.ts · queue when rate limited
// never drop a lead, queue it and retry
export async function enrichLead(lead) {
  try {
    await enrich(lead)
    await sql`update leads set status='enriched' where id=${lead.id}`
  } catch (err) {
    if (isRateLimit(err)) {
      await queue.add('enrich', lead, { delay: 60000 })
    } else throw err
  }
}
app/dashboard/page.tsx · real counts
// counts come from the DB, not fixtures
export async function getCounts() {
  const [{ count }] = await sql`
    select count(*)::int as count from leads
    where created_at > now() - interval '7 days'
  `
  return count
}

Why live matters

A live deploy is a stricter reviewer than a screenshot. If adding a lead does not stick, you find out right away. That feedback loop kept the scope small and the data honest.

Back to work → GitHub profile →