LinkedIn DM
Sends personalized LinkedIn messages to existing 1st-degree connections. Each message has:
- - A personalized opening unique to each person (based on their profile + relationship to the sender)
- A consistent product/pitch section confirmed once by the user and reused for all messages
⚠️ Pre-flight Checklist — Confirm Before Starting
1. Connection List
Ask the user for their data file or list. Must include (or be added):
- - Person Name — full name
- Company/Role — their current company or role
- LinkedIn URL — optional but helpful
- Message Status — column for tracking (add if missing)
If only a plain list is provided, offer to convert to TSV.
2. Read Sender's LinkedIn Profile (mandatory)
Before writing any messages, navigate to
/in/me/ and read the sender's profile:
- - Name and current role/company
- Career history — companies, roles, years
- Education — college, degree, batch years
- Location
Store these facts. They are used to identify relationship hooks with each connection.
3. Confirm the Pitch (once, upfront)
Ask the user:
"What's your pitch / product message? This will be the consistent part of every message. Describe it in 1–2 sentences."
Then draft a polished pitch section (2–4 sentences max, punchy and clear). Show it to the user and get explicit approval. Do not start sending until the pitch is confirmed.
Example prompt: "Here's the pitch I'll use for everyone — confirm or edit:
'I'm building an AI calling agent — you give it a phone number + context, and it handles the call end-to-end. Think customer follow-ups, research calls, vendor coordination — anything phone-based that eats into your day. Happy to show you a demo if this sounds useful.'"
4. Browser Setup
- - Option A — Chrome Browser Relay (
profile="chrome"): extension attached to LinkedIn tab (badge ON) — recommended for flagged accounts - Option B — OpenClaw Isolated Browser (
profile="openclaw"): openclaw-managed Chrome, LinkedIn logged in
5. CRM Sheet
Ask the user for a Google Sheet ID/URL to log outreach results. If they don't have one, offer to set one up (create tab + write headers). Confirm
gog is authenticated (
gog auth list).
If the user skips this, fall back to local linkedin_dm_progress.json but remind them the follow-up skill needs the sheet.
6. Ready Check
Only proceed once:
- - ✅ List is ready
- ✅ Sender profile has been read
- ✅ Pitch is confirmed by user
- ✅ Browser is open with LinkedIn logged in
- ✅ Sheet ID confirmed (or sidecar fallback acknowledged)
Relationship Analysis (per person)
Before writing a message, compare the connection's profile against the sender's profile to find the strongest hook. Use this hierarchy — pick the highest that applies:
| Priority | Hook | Example opener |
|---|
| 1 | Same company (current or past) | "You and I both spent time at CRED…" |
| 2 |
Same college + overlapping years | "Fellow BITS Goa 2018 batch here…" |
| 3 |
Same college (different years) | "BITS connect here — saw your journey from…" |
| 4 |
Same industry/function | "Both been in fintech/product for a while…" |
| 5 |
Mutual connection | "We're both connected to [Name]…" |
| 6 |
Their work context (no personal hook) | "Seen what you've built at [Company]…" |
Combine the hook with a line about their current work to show you know what they do.
Message Structure
Send as two separate messages per person, back to back:
Message 1 — Personalized opener (unique per person)
[Relationship hook — 1 sentence]
[Acknowledgement of their work/role — 1 sentence]
Target: 100–180 chars. Feels like a genuine reach-out from someone who knows them.
Message 2 — Pitch (identical for everyone, confirmed upfront)
[Product description — 1–2 sentences]
[Relevant use case for their role — 1 sentence]
[Soft CTA — 1 sentence]
Target: 150–250 chars. Clear, punchy, no filler.
Why two messages?
- - Opener lands first — they see it before the pitch, feels more personal
- Pitch is clearly a separate thought, not buried at the end
- Mirrors how a human would actually message a connection
Fallback: If sending two messages is technically difficult (e.g. bubble re-focusing issues), use Shift+Enter twice between the opener and pitch to create a paragraph break within a single message.
Do not:
- - Open with "I hope you're well" or "I came across your profile"
- Use the same opening for multiple people
- Change the pitch section per person
Batch Preview Before Sending
Generate messages for the entire list first. Present them in a table:
| Name | Company | Relationship Hook Used | Message Preview |
|---|
| Shorya Saini | Razorpay | Same BITS batch | Hey Shorya, BITS Goa 2018 batch… |
Get user approval on the full batch before opening the browser. Allow edits per row.
Sending Flow (Per Person)
- 1. Navigate to
/feed/ — mandatory, no exceptions, no skipping - Wait 3–5 seconds
- Search connections at
linkedin.com/mynetwork/invite-connect/connections/ — type name in "Search by name" - Handle results:
- 1 match → confirm name + headline → click to open profile
- Multiple matches → show user, ask which one
- 0 matches → mark
Not a Connection, skip
- 5. Read their profile if not already done (for personalisation)
- Click Message button on their profile
- Send Message 1 — personalized opener only, send it
- Send Message 2 — pitch only, send it immediately after
- Confirm both delivered
- Log to CRM sheet — append row via
gog sheets append with all fields (see CRM Tracking section)
See references/browser-workflow.md for exact browser automation steps.
Status Values
| Status | Meaning |
|---|
| INLINECODE12 | Message delivered this session |
| INLINECODE13 |
Recent conversation exists — skip |
|
Not a Connection | No Message button or not in connections search |
|
Profile Not Found | Could not identify the right person |
|
Skipped | User chose to skip |
|
Failed | Browser error — retry next session |
Anti-Detection Rules
- -
/feed/ before every single profile — non-negotiable - 3–5 second wait after feed loads
- Max 15–20 messages per session
- Stop immediately if LinkedIn warns about messaging rate — tell the user
CRM Tracking — Google Sheet
After each message is sent, append a row to a Google Sheet. This sheet is the source of truth for all outreach — current session and future follow-up.
Sheet Setup
Ask the user for a Google Sheet ID or URL at the start of the session (or offer to create a new one). The sheet should have a tab named Outreach with these columns:
| Col | Field | Notes |
|---|
| A | Date Sent | ISO date, e.g. INLINECODE20 |
| B |
Person Name | Full name |
| C | Role / Title | Their current headline from LinkedIn |
| D | Company | Current company |
| E | LinkedIn URL | Profile URL |
| F | Relationship Hook | What hook was used (e.g. "Same batch BITS Goa 2018", "Both at CRED 2022–23") |
| G | Opener Sent | Exact text of Message 1 |
| H | Pitch Sent | Exact text of Message 2 |
| I | Campaign | Short label for this batch (e.g. "AI Calling - Feb 2026") |
| J | Status | Always
Sent when first logged — updated by follow-up skill |
| K | Notes | Anything notable (prior conversation, context, mutual connection used) |
| L | Last Updated | Timestamp of last status change |
Column I (Status) lifecycle — only Sent is written by this skill. The follow-up skill will update to:
Replied · Call Scheduled · Demo Done · Follow Up Sent · No Response · Closed Won · INLINECODE29
Appending a Row
After each message pair is sent, run:
CODEBLOCK2
First-Time Setup
If no sheet exists yet, tell the user:
"I'll need a Google Sheet to track outreach. Share an existing sheet ID/URL, or I can create one with the right columns."
To create a new sheet, use Drive (or ask user to create one and share the ID). Then write the header row:
CODEBLOCK3
Local Sidecar (fallback)
If Google Sheets is not set up, fall back to a local linkedin_dm_progress.json:
CODEBLOCK4
LinkedIn DM
向现有的第一度人脉发送个性化的LinkedIn消息。每条消息包含:
- - 针对每个人的个性化开场白(基于其个人资料及与发件人的关系)
- 用户一次性确认并在所有消息中重复使用的统一产品/推销部分
⚠️ 启动前检查清单 — 开始前请确认
1. 联系人列表
向用户索要数据文件或列表。必须包含(或添加):
- - 姓名 — 全名
- 公司/职位 — 当前公司或职位
- LinkedIn链接 — 可选但有用
- 消息状态 — 用于追踪的列(如缺失则添加)
如果只提供纯文本列表,主动提出转换为TSV格式。
2. 读取发件人的LinkedIn个人资料(必做)
在撰写任何消息之前,导航至 /in/me/ 并读取发件人的个人资料:
- - 姓名和当前职位/公司
- 职业经历 — 公司、职位、年限
- 教育背景 — 大学、学位、入学年份
- 所在地
存储这些信息。它们用于识别与每位联系人的关系切入点。
3. 确认推销内容(一次性,提前完成)
询问用户:
您的推销内容/产品信息是什么?这将是每条消息中统一的部分。请用1-2句话描述。
然后起草一段精炼的推销部分(最多2-4句话,简洁有力)。展示给用户并获得明确批准。在推销内容确认之前,不要开始发送消息。
示例提示:以下是我将为所有人使用的推销内容 — 请确认或编辑:
我正在开发一个AI通话代理 — 你给它一个电话号码+背景信息,它就能端到端地处理通话。适用于客户跟进、调研电话、供应商协调等任何占用你时间的电话事务。如果觉得有用,我很乐意为你演示。
4. 浏览器设置
- - 选项A — Chrome浏览器中继(profile=chrome):扩展程序附加到LinkedIn标签页(徽章开启)— 推荐用于被标记的账户
- 选项B — OpenClaw隔离浏览器(profile=openclaw):OpenClaw管理的Chrome,已登录LinkedIn
5. CRM表格
向用户索要Google Sheets ID/URL以记录外联结果。如果用户没有,主动提出设置一个(创建标签页+写入表头)。确认 gog 已通过身份验证(gog auth list)。
如果用户跳过此项,则回退到本地 linkedindmprogress.json,但提醒用户后续跟进技能需要该表格。
6. 就绪检查
仅在以下条件全部满足时继续:
- - ✅ 列表已就绪
- ✅ 已读取发件人个人资料
- ✅ 用户已确认推销内容
- ✅ 浏览器已打开并登录LinkedIn
- ✅ 表格ID已确认(或已确认使用侧车回退方案)
关系分析(针对每个人)
在撰写消息之前,将联系人的个人资料与发件人的个人资料进行对比,找到最强的切入点。按以下优先级选择最高适用项:
| 优先级 | 切入点 | 示例开场白 |
|---|
| 1 | 同一公司(当前或过往) | 你我都在CRED待过… |
| 2 |
同一大学+重叠年份 | 同为BITS Goa 2018届校友… |
| 3 |
同一大学(不同年份) | BITS校友 — 看到你从…的经历 |
| 4 |
同一行业/职能 | 都在金融科技/产品领域有一段时间了… |
| 5 |
共同好友 | 我们都与[姓名]有联系… |
| 6 |
对方的工作背景(无个人切入点) | 看到你在[公司]的成就… |
将切入点与一句关于对方当前工作的内容结合,表明你了解他们的工作内容。
消息结构
每人发送两条独立消息,连续发送:
消息1 — 个性化开场白(每人不同)
[关系切入点 — 1句话]
[对其工作/角色的认可 — 1句话]
目标:100-180字符。感觉像是认识他们的人真诚地联系。
消息2 — 推销内容(所有人相同,提前确认)
[产品描述 — 1-2句话]
[针对其角色的相关用例 — 1句话]
[温和的号召行动 — 1句话]
目标:150-250字符。清晰、有力、无废话。
为什么发两条消息?
- - 开场白先送达 — 对方在推销内容之前看到,感觉更个性化
- 推销内容作为独立的想法,不会埋没在结尾
- 模仿人类实际联系好友的方式
回退方案: 如果发送两条消息在技术上困难(例如气泡重新聚焦问题),在开场白和推销内容之间使用两次 Shift+Enter 创建段落分隔,合并在一条消息中。
不要:
- - 以希望你一切顺利或我偶然看到你的个人资料开头
- 对多人使用相同的开场白
- 针对不同人修改推销内容
批量预览(发送前)
先为整个列表生成消息。以表格形式呈现:
| 姓名 | 公司 | 使用的关系切入点 | 消息预览 |
|---|
| Shorya Saini | Razorpay | 同为BITS同届 | 嘿Shorya,BITS Goa 2018届… |
在打开浏览器前获得用户对整个批次的批准。允许按行编辑。
发送流程(每人)
- 1. 导航至 /feed/ — 必须执行,无例外,不可跳过
- 等待3-5秒
- 在 linkedin.com/mynetwork/invite-connect/connections/ 搜索联系人 — 在按姓名搜索中输入姓名
- 处理结果:
- 1个匹配 → 确认姓名+头衔 → 点击打开个人资料
- 多个匹配 → 展示给用户,询问选择哪个
- 0个匹配 → 标记为非联系人,跳过
- 5. 读取对方个人资料(如尚未读取,用于个性化)
- 点击对方个人资料上的消息按钮
- 发送消息1 — 仅发送个性化开场白
- 发送消息2 — 仅发送推销内容,立即发送
- 确认两条消息均已送达
- 记录到CRM表格 — 通过 gog sheets append 添加一行,包含所有字段(参见CRM追踪部分)
具体浏览器自动化步骤参见 references/browser-workflow.md。
状态值
近期有对话记录 — 跳过 |
| 非联系人 | 无消息按钮或不在联系人搜索结果中 |
| 未找到个人资料 | 无法识别正确的人 |
| 已跳过 | 用户选择跳过 |
| 失败 | 浏览器错误 — 下次会话重试 |
反检测规则
- - 每次访问个人资料之前必须访问 /feed/ — 不可妥协
- 信息流加载后等待3-5秒
- 每次会话最多15-20条消息
- 如果LinkedIn对消息发送频率发出警告,立即停止并告知用户
CRM追踪 — Google Sheets
每条消息发送后,向Google Sheets添加一行。该表格是所有外联活动的数据源 — 包括当前会话和未来跟进。
表格设置
在会话开始时向用户索要Google Sheets ID或URL(或主动提出创建新表格)。表格应有一个名为 Outreach 的标签页,包含以下列:
| 列 | 字段 | 说明 |
|---|
| A | 发送日期 | ISO日期,例如 2026-02-13 |
| B |
姓名 | 全名 |
| C | 职位/头衔 | 来自LinkedIn的当前头衔 |
| D | 公司 | 当前公司 |
| E | LinkedIn链接 | 个人资料URL |
| F | 关系切入点 | 使用的切入点(例如同为BITS Goa 2018届、均在CRED 2022-23) |
| G | 开场白内容 | 消息1的准确文本 |
| H | 推销内容 | 消息2的准确文本 |
| I | 活动名称 | 本次批次的简短标签(例如AI通话 - 2026年2月) |
| J | 状态 | 首次记录时始终为已发送 — 由跟进技能更新 |
| K | 备注 | 任何值得注意的内容(之前的对话、背景、使用的共同好友) |
| L | 最后更新 | 上次状态变更的时间戳 |
列J(状态)生命周期 — 本技能仅写入已发送。跟进技能将更新为:
已回复 · 已安排通话 ·