
Lead'em
Lead funneling and management CRM for Google Workspace sales teams.
Sales Tech · Founder · 1 month · Team of 1
Problem
The sales team tracked prospects across disconnected Google Sheets — one file per campaign, inconsistent column names, no shared pipeline stages, and no way to know who moved a lead or when. Bulk list drops from list brokers or event exports meant hours of manual column cleanup before anyone could start outreach. ICP fit was judged ad hoc in Slack threads; warming comments and connection notes lived in personal docs. Managers had no daily visibility into rep throughput, and collaboration meant copying rows into yet another sheet.
Context
The org runs on Google Workspace and needed domain-restricted sign-in without separate passwords. Campaigns vary by source — conference attendees, LinkedIn exports, partner referrals — each with different columns but overlapping semantics (company, title, LinkedIn URL). Reps work in small teams with SALES_MANAGER and SALES_AGENT role labels; everyone shares the same workspace data. Outreach still happens manually on LinkedIn, so the CRM accelerates research and drafting without auto-posting. Deployment targets Vercel with Neon Postgres; Pusher is optional but configured for live notifications when env vars are present.
Strategy
- Schema-first campaign types — Reusable lead schemas with ten field types (text, email, phone, URL, number, date, select, multi-select, checkbox, long text). Fields support required flags, uniqueness constraints, and up to two fields surfaced on kanban cards.
- Pipeline campaigns — Each campaign binds to one type, owns ordered stages, and renders a six-column kanban with @dnd-kit drag-and-drop. Stage transitions are audited; filters support text, select, date, and checkbox queries without leaving the board.
- AI-assisted import — Upload CSV or XLSX, parse headers and preview rows, then let Gemini infer a new campaign type or match against existing types with confidence scores and column mappings. Heuristic type inference runs first as a fallback when the API is unavailable.
- Collaboration and qualification — Per-lead comments with @mentions, direct messages, browser notifications via Pusher, workspace ICP evaluation (TARGET / MAYBE / REJECT), daily stage targets, and a LinkedIn warming-comment tool that saves back to a lead's Connection Note field.
Google OAuth with hosted-domain validation gates every authenticated route; managers configure ICP criteria once at /settings/icp.
Architecture
Web application — Next.js 16 App Router with React 19, Prisma 7 on Neon Postgres via @prisma/adapter-pg, and NextAuth v5 (Google provider). Server actions in src/lib/actions/ handle campaigns, leads, imports, ICP, tasks, and messaging. Route groups split authenticated app ((app)/) from the public marketing landing page.
Data model — CampaignType and CampaignTypeField define schemas; Campaign owns LeadStage rows and Lead records with JSON LeadFieldValue entries. LeadImport stores upload metadata, AI analysis, and committed mappings. LeadIcpEvaluation persists Gemini scores with verdict, decision, reasoning, and pain points. Conversation, DirectMessage, Notification, and LeadComment support team collaboration; DailyTarget and LeadStageTransition track rep goals and pipeline history.
Import pipeline — parse-file.ts reads CSV (csv-parse) and XLSX (xlsx); infer-schema.ts heuristically types columns from sample values; import-analysis.ts calls Gemini generateObject for schema inference and campaign-type matching; commit-import.ts bulk-creates leads with validated field values.
ICP engine — WorkspaceIcpProfile holds product description, target industries, employee range, and scoring guidelines. icp-evaluation.ts prompts Gemini with structured Zod output (score 0–10, verdict STRONG/MIXED/NOT_ICP, decision TARGET/MAYBE/REJECT) grounded in lead field context.
Realtime — pusher-server.ts and pusher-client.ts wrap optional Pusher channels (private-user-*, private-lead-*, private-conversation-*). NotificationProvider delivers in-app toasts, browser notifications, and unread counts; /api/pusher/auth authorizes private channel subscriptions.
Execution
Campaign type builder
- Create types at
/campaign-types/newwith drag-ordered fields, select/multi-select options, and kanban card visibility (max two fields) - Edit existing types; campaigns inherit the schema at creation time
Campaign pipelines
/campaigns/newbinds a type and seeds default stages/campaigns/[id]renders the kanban with toolbar filters, lead detail dialog, inline ICP panel, and activity comments- Drag a card between columns to call
moveLeadToStage; transitions log user and timestamp
Bulk import wizard
/import— upload file, preview parsed columns, run AI analysis, review suggested type or pick a matched existing type, map columns, commit leads into a campaign- Import status flow: UPLOADED → ANALYZED → COMMITTED (or FAILED with error message)
ICP qualification
/settings/icp— workspace profile (product, industries, employee band, scoring and exclusion guidelines, score thresholds)- Per-lead evaluation in the kanban detail dialog; results persist with reasoning bullets and automation use cases
Collaboration
- Lead comments with @mention parsing; mentioned users receive
LEAD_MENTIONnotifications /messages— direct messages between workspace users with read receipts and live updates/notifications— inbox with deep links to leads or conversations
Rep tooling
/targets— daily stage-move targets with progress counter on the kanban/tasks— manual and AI-generated follow-ups linked to leads or campaigns/tools/linkedin-comment— paste a LinkedIn post, optionally attach a lead, generate a warming comment via Gemini, copy or save to Connection Note field
Challenges
Schema flexibility vs. validation — JSON field values need per-type Zod validation at import, edit, and kanban-filter time. dynamic-field-input.tsx and kanban-filters.ts must handle all ten field types without duplicating logic.
Import mapping ambiguity — Spreadsheet headers rarely match internal keys. The dual path (heuristic inference + Gemini structured output) covers offline dev and production; AI matching against multiple existing types required confidence thresholds so reps confirm before commit.
Realtime as optional infrastructure — Pusher env vars may be absent in local dev. Client hooks degrade gracefully (no subscription) while server-side notification delivery still persists rows for later fetch.
ICP consistency across campaigns — Different campaign types expose different fields, so evaluation builds a normalized leadContext map from whatever columns exist, instructing Gemini not to invent data missing from the record.
Kanban performance — Large campaigns with hundreds of leads needed client-side filtering (applyKanbanFilters) and a six-column layout with container queries so horizontal scroll stays usable on laptop screens.
Workspace-only auth — ALLOWED_GOOGLE_WORKSPACE_DOMAIN must be enforced at sign-in and on every session refresh; no fallback email/password path for external contractors.
Solution
Campaign types decouple schema design from pipeline execution — one "LinkedIn export" type can back multiple active campaigns without redefining fields. The import wizard's three-step AI pass (parse → analyze/match → commit) turns a raw XLSX into typed leads in minutes instead of manual header renaming.
LeadKanban uses @dnd-kit pointer sensors with optimistic UI via useTransition; failed stage moves toast and revert. NotificationProvider subscribes to private-user-{id} and suppresses duplicate toasts when the user already has the relevant lead dialog or conversation open.
ICP evaluation uses generateObject with strict Zod enums so dashboard filters can bucket TARGET / MAYBE / REJECT reliably. The LinkedIn comment tool closes the loop between AI drafting and CRM storage by writing directly to a typed Connection Note field when the campaign type defines one.
Prisma 7 generates the client to src/generated/prisma with connection URL in prisma.config.ts; seed data demonstrates a full campaign type, stages, and sample leads for local onboarding.
Measurable impact
- Consolidate spreadsheet sprawl — Multiple per-campaign Google Sheets fold into one schema-first CRM with shared auth and pipeline history.
- Cut import setup time — AI column detection and campaign-type matching aim to reduce manual header mapping from tens of minutes to a short review step.
- Consistent ICP application — Workspace ICP profile and per-lead Gemini evaluation apply the same TARGET / MAYBE / REJECT criteria across every campaign type.
- Real-time team awareness — Pusher-driven notifications for mentions, comments, and DMs replace Slack pings about "who owns this lead."
- Visible daily throughput — Stage-move targets and transition logging give managers a lightweight progress signal without a separate reporting tool.
- Faster LinkedIn warming — The comment generator drafts post-specific warming text reps can copy or save directly onto the lead record.
Tech & infrastructure
Tech Stack
Infrastructure
Integrations
Gallery





