Inspiration
Every CRM I've ever used — Salesforce, HubSpot, even newer ones like Clay — is really just a database of what someone typed in six months ago. Before a rep sends a cold email, they still open five browser tabs by hand: LinkedIn, company news, job postings, the website itself, just to find one thing worth mentioning so the email doesn't read like a template. The CRM never does that lookup for you — it just stores whatever's already stale.
I wanted to build the CRM that does the tab-opening for you, automatically, and turns what it finds directly into a usable draft — not a suggestion to "check LinkedIn," but an actual email referencing something real that's happening at that company right now.
What it does
ScoutCRM is a sales pipeline CRM where every lead is backed by live web data instead of a static contact card.
- Add a lead (company + contact), or click Enrich all New to run live enrichment across every unenriched lead in the pipeline in one click.
- Enrichment calls SerpApi live — Google News, Google Jobs, and Google Search — for that specific company, and stores the results as structured "signals": recent news, open roles, web presence. A two-layer filter strips out navigational junk (login pages, privacy policies, "sign up" pages) so only substantive results survive.
- Generate draft sends those live signals to Claude with a prompt that requires the output to reference something specific from them — not a generic "I noticed you're doing great things" opener.
- Drag the lead through the pipeline — New → Researching → Contacted → Qualified → Proposal Sent → Won/Lost — with native HTML5 drag-and-drop and optimistic UI updates.
- Everything sits behind real multi-user auth: sign up, and your leads are yours, scoped and enforced server-side in Xano, not just hidden in the frontend.
How I built it
The stack is Next.js 16 (App Router) on Vercel for the frontend, Xano for the backend (3 tables, 9 REST endpoints, auth-gated per-user scoping written in XanoScript), SerpApi for live signal data, and the Claude API for drafting — built solo, with Claude as my pair-programmer throughout.
I started with the data model — leads, signals, and outreach drafts — and got a working mock version running entirely in-memory so I could iterate on the UI without waiting on the backend. Once the shape was right, I built the real Xano schema and swapped the data layer over behind a single environment variable, so the same code runs against a mock store locally and the live backend in production.
From there it was layered on top: SerpApi enrichment first (single lead, then bulk), then Claude-powered draft generation grounded in whatever signals actually came back, then the pipeline UI with color-coded stages, then drag-and-drop to move leads through it without clicking into each one.
Challenges I ran into
A silent data-corruption bug in the Xano backend. Xano's optional text inputs arrive as "" rather than null when they're omitted from a request. That breaks the obvious $input.field ?? $existing.field pattern for partial updates — ?? only falls back on null, not on an empty string — so a PATCH sending just {"stage": "won"} was silently blanking every other field on the lead. I only caught it by deliberately testing the exact partial-update case against the live API, not just the happy path. The fix was building the update from the existing record and conditionally overwriting only fields where the input was actually non-empty.
SerpApi noise. Early on, "enrichment" was returning things like a company's own login page or privacy policy as a "signal" — technically a real search result, completely useless for outreach. I ended up writing a two-layer filter: excluding known junk patterns at the query level (-inurl:login -inurl:signin -inurl:signup), plus a title-matching heuristic to catch what slipped through. That filter is the difference between the AI drafts referencing something real versus reading like they were fed noise.
HTML5 drag-and-drop, without a library. This Next.js 16 install has enough breaking changes from what's in most training data that pulling in a third-party DnD library felt like a real compatibility risk mid-hackathon, so I built it on the native HTML5 drag-and-drop API directly, with optimistic UI updates that roll back cleanly if the server PATCH fails.
Xano's bulk-insert endpoint. POST /signals/bulk acknowledges with {"success": true} — it doesn't echo back the rows it created. My first version trusted that response as if it were the inserted signals, which silently corrupted the enrichment count shown to the user. Fixed by returning the locally-built signal objects instead of trusting the ack, and re-fetching state where it actually matters.
Accomplishments I'm proud of
Getting the whole loop to actually feel live — clicking one button and watching real SerpApi calls run against seven real companies, then generating a draft that visibly references something from that specific run, not a canned demo. And catching the partial-update bug before it ever showed up in a demo, by testing against the deployed API instead of assuming the happy path was the only path.
What I learned
Mostly that "it works in the mock" and "it works against the real backend" are two different claims, and the gap between them is exactly where the interesting bugs live — Xano's "" vs null behavior, the bulk-insert ack shape, none of that shows up until you actually hit the live API with the exact input a real user would send.
What's next
- Sending the drafted email directly, instead of just generating it (would need an email-sending API like SendGrid or Gmail — out of scope for the hackathon build)
- Team / shared workspaces on top of the per-user auth that's already enforced server-side
- Signal freshness indicators, so a rep can tell at a glance whether a lead's data is from this morning or two weeks ago
Built With
- anthropic-claude
- nextjs
- react
- serpapi
- tailwindcss
- typescript
- vercel
- xano
- xanoscrip
Log in or sign up for Devpost to join the conversation.