Lead'em

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

  1. 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.
  2. 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.
  3. 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.
  4. 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 modelCampaignType 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 pipelineparse-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 engineWorkspaceIcpProfile 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.

Realtimepusher-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/new with 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/new binds 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_MENTION notifications
  • /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 authALLOWED_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

Next.js 16React 19TypeScriptPrisma 7Tailwind CSS 4shadcn/uiNextAuth v5Vercel AI SDKGoogle GeminiZod 4@dnd-kitPusherxlsxcsv-parseBiomeBun

Infrastructure

VercelNeon Postgres

Integrations

Google Workspace OAuthGoogle GeminiPusher

Gallery

Lead'em screenshot
Lead'em screenshot
Lead'em screenshot
Lead'em screenshot
Lead'em screenshot
Lead'em screenshot