Lnkr

Lnkr

LinkedIn Sales Navigator outreach agent — draft-only, human-in-the-loop.

RevOps · Founder · 1 month · Team of 1

Problem

Outbound reps working Sales Navigator saved lists face a repetitive daily loop: open profiles one by one, decide fit against a mental ICP, skim recent posts for a warming angle, and craft a 300-character connection note — all while trying not to blow past LinkedIn rate limits or violate team messaging standards. Spreadsheets and ad-hoc notes do not scale; fully automated bots risk account restrictions and poor personalization. The team needed a system that accelerates research and drafting without removing human judgment from the send step.

Context

The product targets a small outbound sales team using LinkedIn Sales Navigator as the primary lead source. Reps maintain saved people lists in SN; lnkr ingests those lists on a schedule and surfaces the best candidates each day. Compliance is non-negotiable: LinkedIn session auth is manual, scraping carries ToS risk (GPL v3, explicit disclaimer), and every outreach artifact stays in draft status until a rep copies and sends it themselves. Deployment assumes Vercel for the dashboard and AI pipeline, Neon for shared state, and GitHub Actions for headless Playwright sync with cookies stored in GitHub Secrets.

Strategy

  1. Ingestion without serverless Playwright — A dedicated sn-scraper package runs Playwright locally or in GitHub Actions, writing leads to Neon. The Vercel dashboard triggers sync via workflow dispatch and shows readiness when session cookies are configured.
  2. Hybrid ICP scoring — Deterministic rule engine (titles, company size, industry, geo, tech stack, signals) blended with Gemini evaluation. Leads below the fit threshold archive automatically; hard disqualifiers cap fit at 15%.
  3. Daily ranker with deduplication — Top-N batch selected by fitPercent × timing multiplier, with a per-company cap so one account does not dominate the queue.
  4. Draft-only content generation — Gemini Pro writes warming comments (when recent posts or about text exist) and connection notes capped at 300 characters, plus an in-app draft chat for iterative refinement.

Safety controls — daily scrape limits, randomized delays, do-not-contact blocklist, and zero automated LinkedIn actions — are first-class settings, not afterthoughts.

Architecture

Web application — Next.js 16 App Router on Vercel with Prisma 7 on Neon Postgres. Settings cover product profile, ICP criteria, SN list URLs, prompt templates, and safety. Server actions in lib/pipeline/actions.ts orchestrate sync triggers, enrichment batches, scoring, and the daily ranker. The Today view shows the current batch; History stores past batches.

Scraper packagepackages/sn-scraper/ uses Playwright Chromium to log into Sales Navigator via persisted browser profile or exported session cookies, iterate enabled list configs, scrape profile snapshots and recent posts, and respect DAILY_SCRAPE_LIMIT with human-like delays (default 4–10 s between profiles).

GitHub Actions sync.github/workflows/sn-sync.yml runs bun sn:sync --all on weekdays at 11:00 UTC and on manual dispatch. Concurrency group prevents overlapping runs; Playwright browsers are cached between jobs.

Scoring pipelinelib/icp/rules.ts computes weighted dimension scores; lib/icp/scorer.ts blends with Gemini Flash structured output. Results persist to LeadScore with dimension breakdown, fit reasons, disqualifiers, pain points, and timing signal (HOT / WARM / COLD).

Content pipelinelib/agent/daily-ranker.ts enriches stale leads, rescores if needed, ranks candidates, and optionally invokes generate-content.ts for warming comments and connection notes. DraftChat stores conversational refinement per lead.

Enrichment — Pluggable providers: profile (free, derived from scrape data), datalayer, or apollo. Company records cache in CompanyEnrichment with configurable TTL (default 7 days).

Execution

Settings and onboarding

  • Product profile — name, value props, target industries/personas, case studies for draft personalization
  • ICP criteria — titles, seniority, company size range, industries, geo, tech stack signals, exclusion rules, dimension weights, fit threshold (default 45%)
  • SN list config — paste Sales Navigator saved-list URLs, enable/disable per list
  • Prompt templates — editable instructions appended to Gemini prompts for warming comments and connection notes
  • Safety — do-not-contact blocklist, scrape limit visibility, local login guide, GitHub cookie export instructions at /help

Pipeline actions (dashboard)

  • Sync lists (GitHub) — workflow dispatch to scrape enabled SN lists into Neon
  • Run cloud pipeline — Enrich → Score → Build batch (single action or step-by-step)
  • Readiness checks surface missing API keys, unconfigured lists, or expired LinkedIn session

Daily workflow

  • / — Today's ranked batch with fit %, timing badge, warming comment and connection note copy buttons, status tracking (NEW → SENT / SKIPPED)
  • /history — Past daily batches with lead counts and completion stats
  • /leads/[id] — Full profile, score breakdown, drafts, and draft chat for AI refinement
  • Lead statuses: NEW, QUALIFIED, ARCHIVED, SENT, SKIPPED, SNOOZED

CLI scripts (admin / CI)

  • bun sn:sync --login / --all — Playwright sync
  • bun sn:export-cookies — Export session for GitHub Secrets
  • bun enrich:leads, bun score:leads, bun daily:rank — Pipeline steps outside the UI

Content generation

  • Warming comments reference recent LinkedIn posts or about-text fallback
  • Connection notes enforced at 300 characters with sentence-aware truncation
  • Draft chat (DraftChat + DraftChatMessage) allows rep-driven edits via Gemini without re-running the full pipeline

Challenges

Playwright on serverless — Vercel functions cannot run Chromium. Sync had to split across GitHub Actions (production) and optional local fallback, with the dashboard only triggering workflows and reading shared Neon state.

LinkedIn session fragility — Cookies expire; login timeouts stop sync with actionable errors. One-time headed login exports cookies to GitHub Secrets; reps re-export monthly or on failure.

Rate limiting vs. batch size — Default daily scrape limit (10 profiles) aligns with conservative outreach pacing. The ranker, enricher, and scorer share the same batch cap so cloud pipeline steps stay within serverless timeouts.

Hybrid scoring reliability — Gemini outages or quota errors fall back to rule-only scoring without blocking the pipeline. Hard ICP exclusions (competitors, wrong geo) cap fit regardless of LLM enthusiasm.

Personalization without invention — Scoring and content prompts explicitly instruct the model to ground claims in scraped profile data. Warming comments skip generation when neither posts nor about text provide enough context.

Split deployment awareness — UI must explain where each step runs (Vercel vs. GitHub Actions vs. local) so non-technical reps know when to wait for sync vs. click "Run cloud pipeline."

Solution

A monorepo with packages/sn-scraper isolates Playwright from the Next.js app. Shared Prisma schema means sync and dashboard never drift on data shape. lib/runtime/deployment.ts detects Vercel vs. local and adjusts batch limits and sync provider messaging.

The hybrid scorer (scoreLeadHybrid) weights deterministic rules at 40% and LLM fit at 60%, merges disqualifiers and fit reasons from both layers, and logs fallback events to ActivityLog. The daily ranker applies timing multipliers (HOT 1.2×, WARM 1.0×, COLD 0.7×) and enforces MAX_LEADS_PER_COMPANY = 2 during selection.

Pipeline server actions revalidate dashboard paths after each step and return human-readable status strings (daily limit reached, login required, batch already exists). Settings forms use starter config for fitness/wellness ICP examples, editable before first production run.

Measurable impact

  • Eliminate manual list triage — Reps review a pre-ranked daily batch instead of scrolling entire SN saved lists.
  • Draft-only compliance — Zero automated LinkedIn actions; every connection note and warming comment requires explicit copy-and-send.
  • Consistent ICP application — Hybrid scoring applies the same criteria and threshold to every synced profile, archiving poor fits automatically.
  • Reduce note-writing time — AI-generated warming comments and 300-character connection notes aim to cut per-lead research from minutes to a quick review-and-edit.
  • Safe scrape pacing — Hard daily profile limits and randomized delays reduce risk of aggressive automation patterns.
  • Team-ready deployment — GitHub Actions sync lets reps use the Vercel dashboard daily without running Playwright locally.

Tech & infrastructure

Tech Stack

Next.js 16React 19TypeScriptBunPrisma 7PostgreSQLPlaywrightVercel AI SDKGoogle GeminiTanStack TableTailwind CSS 4shadcn/uiZodBiome

Infrastructure

VercelNeon PostgresGitHub Actions

Integrations

LinkedIn Sales NavigatorGoogle GeminiGitHub Actions workflow dispatchApollo enrichmentDataLayer enrichment

Gallery

Lnkr screenshot
Lnkr screenshot
Lnkr screenshot