惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
F
Fortinet All Blogs
博客园_首页
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
D
DataBreaches.Net
aimingoo的专栏
aimingoo的专栏
爱范儿
爱范儿
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
N
Netflix TechBlog - Medium
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
D
Docker
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
罗磊的独立博客
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
博客园 - 三生石上(FineUI控件)
量子位
The Last Watchdog
The Last Watchdog
MyScale Blog
MyScale Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
The Blog of Author Tim Ferriss
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
小众软件
小众软件
Cloudbric
Cloudbric
博客园 - 司徒正美
H
Help Net Security
人人都是产品经理
人人都是产品经理
Application and Cybersecurity Blog
Application and Cybersecurity Blog
L
LangChain Blog
Latest news
Latest news
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
博客园 - Franky
S
Security Affairs
W
WeLiveSecurity
F
Full Disclosure
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News
The Hacker News
The Hacker News
Cyberwarzone
Cyberwarzone
美团技术团队
PCI Perspectives
PCI Perspectives
C
Check Point Blog
Spread Privacy
Spread Privacy

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Build an Intent-Based LinkedIn Outreach System in an Afternoon
Kamal · 2026-05-07 · via DEV Community

I got tired of cold-outreach tools that scrape lists and spray sequences. The reply rates are awful (~0.3% on cold), the recipients hate it, and as a builder it feels like the wrong shape of problem.

So I built the inverse: a system that waits for someone to signal they're in-market — a comment on a competitor's launch post, a job change, a public post saying "open to senior backend roles" — and fires a webhook the moment a match shows up. Your agent reaches out within hours, while the context is still fresh.

This tutorial walks through building the whole loop in an afternoon, on top of the myagentmail-outreach-starter (MIT-licensed). I'll be honest upfront: the starter calls a paid backend (the one I built — myagentmail). The repo's code is yours forever; running it end-to-end uses an API key (7-day free trial, no card). Same shape as Stripe sample apps or Resend examples.

Here's what we're building:

Most outreach is broken because it ignores intent. You build a list, you blast a sequence, and you hope. The reply rate is whatever it is.

There's a more useful version of outreach: wait for someone to signal that they're in-market, then reach out within hours, while the context is still fresh. The signal might be a complaint about their current vendor, a comment on a competitor's launch post, or a job change into a role with budget. Each of those is a moment where a thoughtful, specific connection note has 5–20× the response rate of a cold blast.

This tutorial walks through building that system — the whole loop, from signal capture to drafted message in your approval queue — on top of the myagentmail-outreach-starter repo. About an afternoon's work, three accounts (myagentmail, OpenAI, your LinkedIn), zero infrastructure to operate.

What you're building

┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│   LinkedIn       │    │   MyAgentMail    │    │  Your starter    │
│   - posts        │───▶│   - polls every  │───▶│   - HMAC-verify  │
│   - reactions    │    │     N hours      │    │   - GPT drafts   │
│   - comments     │    │   - LLM filters  │    │     a note       │
│   - job changes  │    │     by your rule │    │   - queues for   │
│                  │    │   - HMAC webhook │    │     approval     │
└──────────────────┘    └──────────────────┘    └─────────┬────────┘
                                                          │
                                                          ▼
                                                ┌──────────────────┐
                                                │  You: 1-click    │
                                                │  Approve →       │
                                                │  Connection sent │
                                                └──────────────────┘

Enter fullscreen mode Exit fullscreen mode

Three things make this loop different from generic outreach tools:

  1. Three signal types, not just keyword search. Watch for posts matching a phrase (keyword), watch for engagers on a specific person or company's posts (engagement), watch for job changes on a list of profiles (watchlist). All three feed the same approval queue.
  2. A plain-English firing rule decides what fires, not just the keyword. The classifier treats your rule as authoritative — "flag founders complaining about cold email; skip vendors selling outbound tools, agencies, content marketers" — and cites a one-sentence reason on every match.
  3. Your real LinkedIn account does the work. No proxy farms, no rotating residential IPs, no shared scraping pools. Connection requests arrive from your actual profile, which is what makes the response rate non-trivial in the first place.

Setup — three accounts, ten minutes

# Step Where Notes
1 Sign up at myagentmail.com https://myagentmail.com?utm_source=devto&utm_medium=article&utm_campaign=intent-outreach-tutorial Free signup.
2 Subscribe to the LinkedIn add-on Dashboard → Billing Solo tier ($29/mo) is enough for this tutorial.
3 Grab your master API key Dashboard → API Keys One key authorizes inbox, LinkedIn, and signals.
4 Get an OpenAI key platform.openai.com The starter uses gpt-4o-mini to draft notes — pennies a day.
5 Clone the starter git clone https://github.com/kamskans/myagentmail-outreach-starter ~3,000 lines of TypeScript. Fork it.
6 Connect a LinkedIn account The starter's /accounts page Either email + password (PIN or mobile-app push) or paste your li_at + JSESSIONID cookies. AES-256-GCM encrypted at rest.

Configure your .env:

MYAGENTMAIL_API_KEY=tk_...
OPENAI_API_KEY=sk-...
# Webhook secret comes after you create your first signal, in the next section.
MYAGENTMAIL_WEBHOOK_SECRET=

Enter fullscreen mode Exit fullscreen mode

npm install && npm run dev. The starter runs on localhost:3000. Use ngrok or cloudflared to expose your laptop to public webhooks if you want signals to fire from the cloud cron — or use the Run now button on each signal to trigger polls manually while you develop.

Pick the right signal kind for what you're tracking

The single most important decision in setting this up is which kind of signal matches your use case. Here's the table I'd want pinned to my desk on day one:

If you want to find… Use this signal kind Example
People publicly describing a pain you solve Keyword "outbound is broken", "our CRM is killing us"
ICPs warming up to a competitor Engagement on the competitor's company page Watch linkedin.com/company/competitor/ → fire on every commenter who's a head of sales
ICPs your champion influencer's audience Engagement on a profile Watch a niche author whose audience IS your ICP → fire on engagers matching role/seniority
Champions who just got budget at a new company Watchlist List of 30 past customers' profile URLs → fire when their new role matches an ICP company

Engagement and watchlist signals are the ones that meaningfully differentiate from generic intent providers. Keyword search is everywhere; engagement-on-a-tracked-actor and job-change-on-a-curated-list are not. Both feed the killer personalization angle: your connection note can quote the engager's own comment back at them, or congratulate the specific role transition — neither of which requires guessing.

Signal 1: keyword — the warm-up

Open the starter at /managed-signals, click New signal, pick Keyword, and fill it in:

  • Name: Founders complaining about cold email
  • Query: outbound is broken
  • Firing rule: "Flag founders or operators complaining about cold email or outbound burnout. Skip vendors selling outbound tools, agencies pitching their services, and content marketers writing thought-leadership posts."
  • Cadence: Daily
  • Webhook URL: https://your-ngrok-url.ngrok.io/api/webhook (or http://localhost:3000/api/webhook if you're triggering polls manually)
  • Filter: Medium and above

Click Create signal. MyAgentMail returns a whsec_... secret — paste it into MYAGENTMAIL_WEBHOOK_SECRET in .env and restart the dev server. From here on, every match arrives signed; the starter's webhook handler verifies the signature before accepting it.

How it works under the hood: every poll runs a two-pass pipeline. Pass 1 is a cheap text-only triage that drops vendor pitches and content marketers. Pass 2 looks up the author's actual role + company on LinkedIn, then runs your firing rule with that verified context. The classifier returns {engage: bool, intent: low|medium|high, reason: "one sentence"}. The reason is shown on every match in your queue — every fire is auditable end-to-end.

Signal 2: engagement — the high-intent one

This is the kind that earns its keep. The premise: someone who engages with a post by an actor you've chosen to track is, by their action, a hand-raised lead. The post is the trigger; the engager is the target.

import { MyAgentMail } from "myagentmail";
const mam = new MyAgentMail({ apiKey: process.env.MYAGENTMAIL_API_KEY! });

await mam.linkedin.signals.createEngagement({
  name: "Acme company-page engagers",
  target: {
    kind: "company",
    url: "https://www.linkedin.com/company/acme/",
  },
  intentDescription:
    "Flag engagers who are heads-of-sales or RevOps at SaaS companies " +
    "between Series A and C. Skip Acme employees, direct competitors, " +
    "and recruiters.",
  webhookUrl: "https://your-ngrok-url.ngrok.io/api/webhook",
  filterMinIntent: "medium",
});

Enter fullscreen mode Exit fullscreen mode

When it fires, the starter receives a signal.engagement payload like this:

{
  "type": "signal.engagement",
  "signal": { "id": "...", "name": "Acme company-page engagers", "kind": "engagement" },
  "match":  { "id": "...", "foundAt": "2026-04-28T14:22:11Z" },
  "target": { "kind": "company", "url": "https://www.linkedin.com/company/acme/", "label": "Acme Inc." },
  "post": {
    "url": "https://www.linkedin.com/feed/update/urn:li:activity:.../",
    "excerpt": "We shipped real-time replay this morning..."
  },
  "engager": {
    "name": "Jane Smith",
    "profileUrl": "https://www.linkedin.com/in/jane-smith/",
    "headline": "VP RevOps at Beta",
    "role": "VP RevOps",
    "company": "Beta",
    "action": "commented",
    "commentText": "We're hitting this exact problem at Beta — DM'd you."
  },
  "classification": { "engage": true, "intent": "high", "reason": "..." }
}

Enter fullscreen mode Exit fullscreen mode

The starter's webhook handler dispatches on event.type and routes engagement matches to a kind-specific drafter (draftEngagementConnectMessage in src/lib/agent.ts). This drafter quotes the engager's verbatim comment back at them — the killer personalization angle:

"Jane — saw your comment on Acme's replay launch. We hit the same problem at Beta last quarter; happy to share what worked. Open to connecting?"

That note has a 3–5× higher acceptance rate than a generic "saw your post, would love to connect" — because it proves you actually read what they wrote.

A note on cadence: engagement signals fan out hard (multiple posts × reactions + comments per poll), so sub-daily cadence is gated by tier. Solo (2 sessions) clamps to daily; team (15 sessions) unlocks every_12h; agency (60) unlocks every_6h. Connect more accounts to run more frequent polls.

Signal 3: watchlist — the slow burn

Job changes are warm-intro gold for the first 30–60 days. A VP of Sales who just moved from Beta to Acme has new budget, new tools to evaluate, and zero loyalty to incumbent vendors at Acme yet. If you've kept a list of ex-customers, ex-coworkers, or champions at past accounts, this signal converts that list into a perpetual "tell me when one of these people gets new budget" feed.

await mam.linkedin.signals.createWatchlist({
  name: "Past champions on the move",
  profileUrls: [
    "https://www.linkedin.com/in/ex-champion-one/",
    "https://www.linkedin.com/in/ex-champion-two/",
    // ...up to 500 entries per signal
  ],
  intentDescription:
    "Fire when this person's NEW role is at a SaaS company between $5M and " +
    "$100M ARR and the title implies budget over outbound or sales tooling. " +
    "Skip moves into agencies and consultancies.",
  webhookUrl: "https://your-ngrok-url.ngrok.io/api/webhook",
  filterMinIntent: "medium",
});

Enter fullscreen mode Exit fullscreen mode

Each entry is polled weekly through MyAgentMail's profile cache. The first poll is a snapshot — it records the role/company without firing. From the second poll onward, role/company diffs trigger the firing rule with the new role + company, so your rule decides whether the move is one you care about.

When it fires:

{
  "type": "signal.job_change",
  "signal": { "id": "...", "name": "Past champions on the move", "kind": "job_change_watchlist" },
  "match":  { "id": "...", "foundAt": "2026-04-28T..." },
  "person": { "name": "Jane Doe", "profileUrl": "https://www.linkedin.com/in/jane-doe-eng/", "headline": "VP Engineering at Acme" },
  "change": { "oldRole": "Director of Engineering", "oldCompany": "OldCo", "newRole": "VP Engineering", "newCompany": "Acme" },
  "classification": { "engage": true, "intent": "high", "reason": "..." }
}

Enter fullscreen mode Exit fullscreen mode

The starter routes this to draftJobChangeConnectMessage, which writes a specific congratulatory opener — "Jane — saw the move from OldCo into the VP Eng seat at Acme. Curious what your first 90-day priorities look like; happy to share what worked when [past customer] made the same jump" — instead of the generic "congrats on the new role!" every recruiter sends.

What the queue looks like

Every match — regardless of kind — lands as one row in your approval queue at /queue with three fields:

  • The drafted connection note (280 chars, GPT-4o-mini, ready to send)
  • The reasoning line ("Engaged on Acme Inc. (commented) — VP RevOps at Beta matches our ICP — intent: high")
  • A one-click Approve that calls myagentmail's /v1/linkedin/connections and sends the request from your actual LinkedIn account

You stay in the loop on every send — that's the point. The agent does the work of finding signal, classifying it, and drafting the note. You spend 30 seconds reviewing. The whole approval queue is in src/app/queue/page.tsx if you want to swap the UI for something else.

Wire it for production

A few things to do once the basic loop works:

1. Multi-account routing. Connect a second and third LinkedIn account in the starter's /accounts page. Then create signals with sessionId: null (the default) — myagentmail's session router automatically distributes polls across all healthy sessions, multiplying your daily quota by the number of accounts you've connected. Sub-daily cadences require this.

2. Public webhook endpoint. Deploy the starter to Vercel. Update each signal's webhook URL to point at your deployed domain. The starter reads MYAGENTMAIL_WEBHOOK_SECRET per signal — if you run multiple signals with different secrets, change src/app/api/webhook/route.ts to look up the right secret per signal ID.

3. Tune the firing rules. The classifier treats your intentDescription as authoritative. If you're getting too many false positives, add explicit "skip" examples ("skip recruiters posting job ads", "skip authors at competing vendors"). If you're getting too few matches, loosen the role/seniority constraints.

4. Tune the drafters. The three drafters in src/lib/agent.ts are short prompts. Edit them to match your voice. Add a productPitch argument if you want the note to mention what you do; remove the constraint on emoji if your audience uses them. The whole thing is ~40 lines.

5. Watch the parse-rate logs. Engagement signals do a small amount of LinkedIn page scraping under the hood (LinkedIn migrated comment text off their public API last year). MyAgentMail emits a structured log line per poll — [engagement-poll] signal=X linkedin_comments=20 parsed_comments=18 — so when their next deploy breaks the parser you'll notice within a day instead of a month. If parsed_comments drops to zero across multiple polls, file a ticket; we re-derive parsers from real customer payloads, fix usually within hours.

What you've shipped

In the time it took to read this, you have:

  • Three live LinkedIn signals — one keyword, one engagement, one watchlist — running on your own connected account on a daily cadence
  • An HMAC-verified webhook handler that drafts a personalized note per match using GPT, with a different drafter per signal kind
  • An approval queue you can review in 30 seconds per match
  • A one-click connection-request flow that sends from your real profile

That's the whole intent-based outreach loop. The rest is product judgment: which signals to add, how to tune the firing rules, how aggressive to be in approving. The infra is done.

Resources

If you build something interesting on top of this, send it our way. The starter is intentionally minimal — three thousand lines of forkable code — because we'd rather you customize it than wait for us to ship features.

Things I'd love honest feedback on

  1. The plain-English firing rule — feature or footgun? Letting users describe filtering as "Senior ICs, skip recruiters, skip current employees of <us>" skipped a whole filter UI but pushes complexity into prompt-quality. I think it's the right call but I'd love to be wrong.

  2. The "open source on top of paid backend" framing — does that read as honest or as marketing? Trying to get the wording right. I want the repo to be genuinely useful as reference even if you can't run it.

  3. What intent signals do you actually use? I shipped three (job change, engagement, keyword) because they're the ones I'd run myself. If you're doing intent-based outreach in production, what am I missing?

If you build something on top of this, I'd love to see it. Reply here or DM me on LinkedIn — comment thread on this post is a great place for architecture questions too.

Repo: https://github.com/kamskans/myagentmail-outreach-starter
Full writeup: https://myagentmail.com/blog/intent-based-outreach-tutorial?utm_source=devto&utm_medium=article&utm_campaign=intent-outreach-tutorial