Sales Navigator Outreach System · TrustNet · 2025
The Sales Nav tool you can't buy.
“I paid for the tier LinkedIn won't integrate. So I built the integration myself, inside ToS.”
The context
Sales Navigator is the most expensive product in B2B prospecting that still hands you a worse workflow than a free Sheet.
The tier I paid for couldn't sync to HubSpot. The tier that could was a different price bracket. Below Advanced Plus, every connection request, message, comment, and post becomes a row in a personal tracker you maintain by hand. I ran the full TrustNet sales cycle solo for three years and the Sales Nav tracker was eating two to three hours a day in record-keeping and message personalization.
The third-party tools that promise to fix this mostly break ToS in ways that range from "you'll get flagged" to "you'll lose your account." I wasn't willing to take that bet on the surface that mattered most to my pipeline. So I built my own, with the explicit constraint that nothing about it could violate ToS.
The constraint
Two constraints, one on each side.
On the generation side, personalization at volume is a quality cliff. One great message takes ten minutes of reading the profile and surfacing the right hook. Writing fifty in a day means most of them sound like the same prompt ran fifty times. The constraint wasn't speed. It was avoiding the AI-tells every Sales Nav buyer learned to skim past in 2024.
On the action side, LinkedIn's UI is hostile to automation by design. Locators move, class names rotate, modals appear and disappear. Any approach that pretends to be a different user trips the platform's detection layer. The only honest path was working in-session through a browser the platform already trusts.
The decision
The system is two halves with a clean handoff between them.
The generation half is a Google Apps Script bound to a Sheet. Enrichment runs in-Sheet at zero token cost, the Sheet itself is the operator interface, and approved rows flip a column to ready-to-send. The Sheet is the dashboard, no separate UI to build.
The action half is a local TypeScript agent driving a persistent Playwright Chrome profile. The persistent profile is the central decision: a real Chrome instance that's been logged into LinkedIn for months, with cookies and fingerprint of an actual returning user. No detection signal to fire on because there's nothing to detect.
Inside the agent, three layers do three different jobs. An outer dispatcher reads the Sheet and finds approved rows. A middle layer (the only place LLM tokens get spent) reasons about what to say to this specific lead given their signals. An inner layer of browser primitives executes the steps: open profile, navigate to composer, paste text. Reasoning is separated from action is separated from orchestration. When LinkedIn changes its DOM, only the inner layer needs work. When message quality drifts, only the middle layer needs adjustment.
The most important piece: the inner layer doesn't include a send primitive. The agent composes the message inside the LinkedIn dialog and leaves the cursor on the send button. The send is mine.
Annotation (arrow pointing at the gap between agent and send): "the agent does the work. The human takes the action."
The tradeoff
The Apps Script half locks generation to Google's ecosystem. Moving enrichment off Sheets means a rewrite. I accepted this for zero-token enrichment and a deployment story so simple it doesn't exist.
The agent half is local-only. It runs when my laptop is open and can't be moved to a cloud VM without breaking the persistent-profile model. The throughput ceiling is real. I accepted it because the alternative was either ToS risk or burned account risk, and the system's job isn't to scale infinitely. Its job is to take a three-hour daily workflow and compress it to thirty minutes of review.
The result
The headline isn't time saved. It's cost architecture.
Apps Script does the enrichment without consuming LLM tokens. Sheets is free. The deterministic work (cleaning, structuring, scoring, formatting) runs at zero variable cost. Tokens only get spent on the one part of the system that actually requires reasoning. That separation isn't an accident. It's the result of asking where does intelligence actually need to be? and routing everything else to free deterministic compute. Most builds skip this question and default to LLM-everywhere.
Downstream: three-hour Sales Nav workflow now takes thirty minutes of review. Message quality holds across leads because each one is generated against its own signal set. Response rates are higher than templated outreach, though attribution is qualitative at this volume.
What I'd do differently
The agent half is V0.5. Playwright locators on LinkedIn break roughly every other week when a class name rotates. The next iteration is semantic queries over aria-labels with a fallback chain, plus better wait strategies for slow-paint modals. Unglamorous but tractable work.
The bigger move I'm thinking about: drop the "approved" column entirely and let the agent decide whether a lead is worth messaging at all given its signals. Then I approve the agent's reasoning, not the lead. The human gate shifts from "approve the action" to "approve the judgment." Higher leverage, more honest test of whether the agent's reasoning is good enough to trust.
The build is always the easy part. The judgment layer is the work.