Software
Lead Qualification System
Split-service pipeline that imports local business leads, scores each one on website opportunity, and syncs only the ones a human approves to HubSpot.
Started
May 09, 2026
Updated
May 16, 2026
Repository
—
Live
—
CONTEXT
Built for a WebsiteThis client through my web development business. Details here are generalized to keep the client's identity out of it — the architecture and technical decisions are the real thing.
Problem
Lead sourcing produces volume; qualifying leads takes judgment. The gap between "here are 500 businesses in this city and category" and "here are the 40 worth a human's time" is exactly where this system lives.
Architecture
Split into two services on purpose, so a slow web audit or screenshot job never blocks the dashboard someone's actively using:
- Web app — a Next.js App Router dashboard: CSV import, a swipe-style review queue, lead detail pages, and job orchestration.
- Worker service — a BullMQ background processor: web audits, screenshots, deduplication, scoring, and the HubSpot sync, all queued rather than run inline.
- Postgres — source of truth for leads, audits, decisions, and job logs, via Prisma.
- Redis — the BullMQ broker connecting the two services.
Lead sourcing itself runs through a configurable Google Maps actor (via Apify) for industry + city searches, with the actor's estimated cost shown before a run and the actual Apify-reported charge shown after.
Interesting technical decisions
- Deterministic scoring is the default, not a fallback. AI-assisted scoring is wired in but explicitly optional — the product stays fully functional on rule-based opportunity scoring alone if no AI provider is configured.
- SSRF-safe URL validation has its own test coverage. Auditing an arbitrary lead-submitted URL means fetching untrusted addresses; validating and normalizing those URLs before anything touches them is treated as a first-class, tested concern rather than an afterthought.
- Deduplication runs on a fingerprint, not just a URL match, so the same business found through two different searches doesn't create two leads to review separately.
- Approval is a hard gate before HubSpot sync — nothing reaches the CRM until a human has actually looked at it, which was the whole point of building a review queue instead of piping raw scraped leads straight in.
Deployment
Ships as separate Coolify resources for the web app, worker, Postgres, and Redis, so the web and worker sides can be redeployed independently — deploying a worker fix doesn't require restarting the dashboard.