关于 linkedin-outreach
linkedin-outreach 是一项技能,可从 Supabase 选取合格线索、编写个性化 LinkedIn 消息序列并记录触达。它为用户选择的触达工具生成 CSV,而不自动操作这些基于浏览器的工具。
主要功能
- 01从 Supabase 选择符合条件的线索
- 02应用筛选条件和 84 天冷却期
- 03生成个性化消息序列
- 04遵循 LinkedIn 字符数限制
- 05导出适配工具的 CSV 文件
- 06将触达记录写入 Supabase
适用场景
- 01为合格线索准备 LinkedIn 活动
- 02生成 Dripify 或 Expandi 导入文件
- 03排除跨渠道近期联系过的线索
- 04在 Supabase 记录触达活动
MCP 相关
该技能不通过 API 或 MCP 自动操作 LinkedIn 触达工具,而是生成供手动导入的 CSV 文件。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ gooseworks-ai/ goose-skills --skill linkedin-outreach安装后在 Claude Code 中输入/linkedin-outreach确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
skill.meta.jsontemplates/tone-presets.jsontemplates/sequence-templates/competitor-engagement.mddatabase-search.mdevent-attendee.mdhiring-signal.mdkol-engagement.mdpain-language.md详细介绍
LinkedIn 触达
这是 cold-email-outreach 的 LinkedIn 对应流程。从 Supabase 获取符合条件的潜在客户,生成个性化的 LinkedIn 消息序列,导出到用户使用的 LinkedIn 触达工具,并将所有记录写回 Supabase。
**与工具无关:**询问用户使用哪款 LinkedIn 工具。所有工具都基于 CSV 导入——LinkedIn 工具(基于浏览器)不提供 API/MCP 自动化。适配器负责处理各工具的列映射和格式差异。
何时自动加载
在以下情况下加载此技能:
- 用户提到“LinkedIn 触达”“在 LinkedIn 上联系这些潜在客户”“发送 LinkedIn 消息”“设置 LinkedIn 营销活动”
- 上游技能通过“创建 LinkedIn 营销活动”或“传递:supabase-eligible-leads”连接,并且用户指定 LinkedIn
- 用户完成
lead-qualification,希望通过 LinkedIn 联系潜在客户
支持的触达工具
此技能不会默认使用某款特定工具。它会先询问,然后进行适配。
| 工具 | 集成方式 | 工作原理 |
|---|---|---|
| Dripify | CSV 导入 | 生成符合 Dripify 导入格式的 CSV,用户手动上传 |
| Botdog | CSV 导入 | 生成包含 Botdog 兼容列的 CSV |
| Expandi | CSV 导入 | 生成符合 Expandi 导入格式的 CSV |
| PhantomBuster | CSV 导入 | 为 PhantomBuster LinkedIn 消息序列生成 CSV |
| 手动 / 其他 | CSV + 说明 | 将潜在客户和消息导出为通用 CSV,并提供设置说明 |
工具选择逻辑:
- 在第 0 阶段询问用户:“你使用哪款 LinkedIn 触达工具?”
- 根据用户的选择生成对应工具的导入 CSV
- 如果是其他工具或未知工具 → 生成通用 CSV(
linkedin_url、first_name、last_name、company、title、connection_request、followup_1、followup_2、followup_3、inmail_subject、inmail_body),并询问用户所用工具的导入要求
前提条件
Supabase
必须根据 tools/supabase/schema.sql 中的架构将人员信息存储在 Supabase 中。people 和 outreach_log 表必须存在。如果是首次设置,请运行 python3 tools/supabase/setup_database.py。
.env 中的环境变量:
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...
LinkedIn 工具
只需导出 CSV——无需 API 密钥。用户手动将 CSV 导入其工具。
字符数限制
LinkedIn 对字符数有严格限制。所有生成的消息都必须遵守这些限制。
| 消息类型 | 限制 | 备注 |
|---|---|---|
| 好友申请附言 | 300 个字符 | 硬性限制。每个字符都计入。 |
| 普通消息 | 8,000 个字符 | 对方接受好友申请后发送 |
| InMail 主题 | 200 个字符 | 仅限 InMail(高级功能) |
| InMail 正文 | 1,900 个字符 | 仅限 InMail |
**限制执行:**生成任何消息后,都要统计字符数。如果超过限制,请改写——不要截断。截断后的消息看起来会不完整。
第 0 阶段:信息收集
一次性询问所有问题,并按类别整理。跳过上游技能已回答的问题。
营销活动目标
- 目标是什么?(预约会议、促成演示申请、获得回复、建立关系、持续培育)
- 触达切入点或吸引点是什么?(招聘信号、替代竞争对手产品、基于活动、基于痛点、陌生人数据库、关键意见领袖互动、共同联系人)
- 这次营销活动应该叫什么?
触达工具
- 你使用哪款 LinkedIn 触达工具?(Dripify / Botdog / Expandi / PhantomBuster / 其他 / 只需要 CSV)
线索筛选
- 我们应该锁定哪些线索?选项:
- 特定
client_name的所有线索 - 特定
icp_segment - 职位名称模式(例如,“运营副总裁”“销售总监”)
- 行业或地点筛选条件
- 高于某个阈值的
qualification_score - 特定
source(crustdata、apollo、linkedin 等) - 自定义筛选条件(描述你的需求)
- 特定
- 有哪些排除项?(特定公司、近期联系过的线索、某些职位)
- 营销活动的最大规模是多少?(默认值:100 — LinkedIn 工具的每日限制低于电子邮件)
语气与风格
- 选择哪种语气预设?请展示以下选项:
- 随和专业 — 友好、自然,略带非正式感。像是在给同行发消息。(默认)
- 思想领袖 — 以洞见或反常观点开篇,树立发送者的专家形象。
- 挑衅式 — 挑战固有观念,打破思维定式。风险更高,回报也更高。
- 企业正式 — 精炼、条理清晰。适用于受监管行业或高管目标人群。
- 自定义 — 粘贴之前效果不错的参考消息,或描述你想要的风格。
- 有哪些效果不错的参考消息?(粘贴示例 — 这些示例会覆盖语气预设)
序列结构
- 建立联系后发送几条跟进消息?(默认值:3)
- 消息之间间隔多久?(默认值:第 0 天建立联系 / 第 3 天跟进 1 / 第 7 天跟进 2 / 第 14 天跟进 3)
- 对于未接受联系请求的线索,是否将 InMail 作为单独步骤?(默认值:是)
个性化
- 这些线索有哪些信号数据?(评论文本、互动过的帖子、共同联系人、招聘信号、活动出席情况)
- 有哪些可引用的证明材料或案例研究?(客户名称、指标、评价)
阶段 1:从 Supabase 筛选线索
连接
使用共享的 Supabase 客户端:
import sys, os
sys.path.insert(0, os.path.join("tools", "supabase"))
from supabase_client import SupabaseClient
client = SupabaseClient(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_ROLE_KEY"])
构建筛选条件
将用户条件映射为 people 表上的 PostgREST 查询参数:
| 用户表达 | PostgREST 筛选条件 |
|---|---|
| “运营副总裁” | title=ilike.*VP Operations* |
| 客户“happy-robot” | client_name=eq.happy-robot |
| 评分 > 7 | qualification_score=gte.7 |
| 有 LinkedIn URL | linkedin_url=neq.(非空) |
| 行业“物流” | industry=ilike.*logistics* |
| 地点“旧金山” | location=ilike.*San Francisco* |
| 来源“crustdata” | source=eq.crustdata |
| 84 天内未联系 | or=(last_contacted.is.null,last_contacted.lt.{84_days_ago}) |
重要: 对于 LinkedIn 外联,人员必须有 linkedin_url。筛除没有该字段的人员 — 无法通过 LinkedIn 联系他们。
冷却期筛选(必须执行)
始终排除过去 84 天(12 周)内通过任何渠道(电子邮件或 LinkedIn)联系过的人员。此项不可省略。
使用共享客户端的 check_cooldown() 方法:
in_cooldown = client.check_cooldown(client_name="happy-robot", cooldown_days=84)
# Returns set of person_id strings still in cooldown
或直接查询:
- 查询
outreach_log,找出sent_date在过去 84 天内的person_id:GET /rest/v1/outreach_log?select=person_id&sent_date=gte.{84_days_ago}&status=neq.bounced&client_name=eq.{client} - 将这些
person_id收集到排除集合中 - 在人员查询中添加
id=not.in.({excluded_ids})
注意: 冷却期适用于所有渠道。30 天前收到过电子邮件的人员,仍处于 LinkedIn 联系的冷却期内。这样可以避免通过多个渠道对同一人员进行轰炸式联系。
展示并确认
展示包含 10-15 条线索的示例表格,其中包括:
- 姓名、职位、公司、行业、评分、LinkedIn URL、上次联系时间、信号类型
告知用户:符合条件的线索总数、因冷却期排除的数量,以及因缺少 LinkedIn URL 排除的数量。
请用户在继续之前确认或调整筛选条件。
阶段 2:序列设计
撰写任何文案之前,先以表格形式呈现序列计划:
| 步骤 | 时间 | 消息类型 | 方法 | CTA |
|---|---|---|---|---|
| 1 | 第 0 天 | 添加好友请求(300 个字符) | 基于信号的个性化备注 | 轻量——只需建立联系 |
| 2 | 第 3 天 | 后续跟进 1(对方接受后) | 价值优先:见解、资源或观察 | 提问或提供帮助 |
| 3 | 第 7 天 | 后续跟进 2 | 社会认同或案例研究 | 明确的请求 |
| 4 | 第 14 天 | 后续跟进 3 | 结束联系/最后一次触达 | 留下沟通机会 |
| 5 | 第 7 天* | InMail(如果对方尚未接受) | 附带背景信息的独立推介 | 会议邀请 |
*如果潜在客户到第 7 天仍未接受添加好友请求,则向其发送 InMail。
与电子邮件序列的主要区别:
- 添加好友请求是第一道关卡——必须让对方愿意接受。备注中不要推销。
- 后续跟进应像对话,而不是群发消息。读起来应该像私信,而不是电子邮件。
- 除 InMail 外,不要使用主题行。
- 消息通常越短越好。在 LinkedIn 上,2 句话的消息效果胜过 5 句话的消息。
在第 3 阶段生成文案之前,先让用户批准结构。
阶段 3:消息生成
直接在此技能中生成消息(无需外部子技能——LinkedIn 消息足够简短,可以直接处理)。
根据互动信号选择模板
根据潜在客户的信号数据选择合适的序列模板:
| 信号类型 | 模板 | 关键个性化变量 |
|---|---|---|
| 使用痛点语言的互动者(有评论文本) | templates/sequence-templates/pain-language.md | {comment_snippet}、{pain_topic} |
| 互动过竞争对手帖文的用户 | templates/sequence-templates/competitor-engagement.md | {competitor_name}、{post_topic} |
| 互动过 KOL 帖文的用户 | templates/sequence-templates/kol-engagement.md | {kol_name}、{post_topic} |
| 数据库搜索(信号较弱) | templates/sequence-templates/database-search.md | {title}、{company}、{industry} |
| 招聘信号 | templates/sequence-templates/hiring-signal.md | {role_hiring_for}、{job_posting_detail} |
| 活动参与者 | templates/sequence-templates/event-attendee.md | {event_name}、{event_topic} |
语气校准
- 从
templates/tone-presets.json加载选定的语气预设 - 如果用户提供了参考消息,则以参考消息为准——分析参考消息的语气、长度、结构和用词
- 将语气指南应用于所有生成的消息
校准流程
- 先为 3–5 位潜在客户生成示例消息(选择信号丰富程度不同的潜在客户)
- 呈现给用户审核——展示每位示例潜在客户的完整序列
- 反复调整直至获得批准(最多 3 轮)
- 获得批准后,批量生成其余消息
写作指南
添加好友请求(最多 300 个字符):
- 以信号开头(对方做了什么/说了什么,引起了你的注意)
- 用一句话说明关联性(你为什么要建立联系)
- 不要推销、不要使用 CTA、不要说“我很乐意……”——只要有趣到足以让对方接受即可
- 必须少于 300 个字符。逐个字符计数。
后续跟进 1(价值优先):
- 简短地感谢对方建立联系(一小句即可,不要写成完整句子)
- 分享真正有用的内容:见解、文章,或对其公司/行业的观察
- 以问题结尾,不要推销
后续跟进 2(社会认同):
- 提及相关客户或案例研究
- 将其与对方的具体情况联系起来
- 提出明确、低承诺的请求(15 分钟通话、异步提问)
后续跟进 3(结束联系):
- 表明你之前曾尝试联系对方
- 用一句话回顾价值
- 不施加压力,为后续沟通留出空间
- 序列中最短的一条消息
InMail(独立推介):
- 主题:最多 200 个字符——以激发好奇心为主,而非推销
- 正文:最多 1,900 个字符——必须能独立成文,因为对方尚未接受你的好友请求
- 说明你联系对方的背景(信号)
- 即使对方从未听说过你,也必须能让其理解
合并变量
所有潜在客户均可使用的标准变量:
{first_name}、{last_name}、{company}、{title}、{industry}、{location}
特定信号变量(根据来源提供):
{comment_snippet}— 对方 LinkedIn 评论的文本{pain_topic}— 对方参与互动的痛点{competitor_name}— 对方参与互动的竞争对手帖子所属的竞争对手{kol_name}— 对方参与互动的 KOL 帖子所属的 KOL{post_topic}— 帖子的主题{event_name}— 对方参加的活动{role_hiring_for}— 对方正在招聘的职位{job_posting_detail}— 招聘信息中的一项详情
第 4 阶段:导出营销活动
步骤 1:生成通用 CSV
所有导出的核心列:
linkedin_url, first_name, last_name, company, title, connection_request, followup_1, followup_2, followup_3, inmail_subject, inmail_body
步骤 2:按所选工具设置格式
Dripify:
- 列映射:
Profile URL→ linkedin_url,Note→ connection_request,Message 1→ followup_1,等等。 - Dripify 要求每位潜在客户占一行,所有消息分别放在不同的列中
- 导出格式:CSV,表头与 Dripify 的导入模板一致
Botdog:
- 列映射:
linkedin_profile_url→ linkedin_url,connection_note→ connection_request,message_1→ followup_1,等等。 - 导出格式:CSV
Expandi:
- 列映射:
LinkedIn URL→ linkedin_url,Connection message→ connection_request,Follow-up #1→ followup_1,等等。 - 支持 InMail 列:
InMail subject、InMail message - 导出格式:CSV
PhantomBuster:
- 列映射:
profileUrl→ linkedin_url,message→ connection_request - PhantomBuster 通常一次处理一个操作——可能需要分别为连接请求和后续跟进创建 CSV 文件
- 导出格式:CSV
手动 / 其他:
- 使用通用 CSV 格式
- 提供列说明和与工具无关的导入说明
- 询问用户的工具需要哪种格式;如有需要,进行调整
步骤 3:保存文件
skills/linkedin-outreach/output/{campaign-name}-{YYYY-MM-DD}.csv
如果 output/ 目录不存在,请创建该目录。
步骤 4:可选的 Google 表格
如果用户需要审核表格,请使用 google-sheets-write 功能创建一个表格,包含:
- 标签页 1:潜在客户列表及所有消息(每位潜在客户占一行)
- 标签页 2:序列模板(使用的主模板)
- 标签页 3:营销活动配置摘要
第 5 阶段:审核与批准
展示营销活动摘要:
Campaign: {name}
Tool: {dripify/botdog/expandi/etc.}
Leads: {count}
Sequence: Connection + {followup_count} follow-ups + InMail
Timing: Day 0 → Day {last_day}
Tone: {preset_name}
Signal types: {breakdown by signal type}
Leads with rich signal: {count} ({percentage}%)
Leads with lean signal: {count} ({percentage}%)
Export file: {file_path}
强制审批关卡
**未经用户明确确认,切勿将营销活动标记为已就绪。**展示摘要,然后询问:“准备好定稿了吗?输入 ‘yes’ 将其标记为可导入。”
批准后:
- 告知用户文件已准备好,可导入其 LinkedIn 工具
- 提供文件路径
- 提供针对相应工具的导入说明(参见第 4 阶段)
- 提醒用户导入后检查前 5-10 条消息是否正确
第 6 阶段:记录日志
数据库写入策略
**本阶段的所有数据库写入都必须事先获得用户在第 5 阶段最终确认关卡中的批准。**由于 LinkedIn 营销活动始终以导出方式交付(从不自动启动),写入 outreach_log 前请先向用户确认——他们可能尚未将营销活动导入 LinkedIn 工具。只有在用户确认导出内容已定稿后,才能记录日志。
记录到 Supabase
导出并获得用户确认后,将记录插入 outreach_log:
POST /rest/v1/outreach_log
Prefer: return=minimal
[
{
"person_id": "{person_uuid}",
"campaign_name": "{campaign_name}",
"channel": "linkedin",
"tool": "{dripify/botdog/expandi/phantombuster/manual}",
"sent_date": "{ISO timestamp}",
"status": "exported",
"client_name": "{client_name}"
},
...
]
或者使用共享客户端:
client.log_outreach(entries)
**状态为 "exported",而非 "sent"。**LinkedIn 工具基于浏览器运行——我们无法确认是否送达。用户确认已在工具中启动营销活动后,状态才会变为 "sent"。
更新人员记录
更新人员表中本次营销活动涉及的所有人员的 last_contacted:
PATCH /rest/v1/people?id=in.({person_ids})
{ "last_contacted": "{ISO timestamp}" }
展示摘要
Campaign: {name}
{count} people logged to outreach_log (channel: linkedin)
last_contacted updated for {count} people
Cooldown active until: {date + 84 days}
Next eligible re-contact: {date}
File ready: {file_path}
冷却期执行规则
本节介绍整个技能中使用的冷却期逻辑。与 cold-email-outreach 共享。
| 规则 | 详情 |
|---|---|
| 默认冷却期 | 从 sent_date 起 84 天(12 周) |
| 跨渠道 | 冷却期适用于电子邮件和 LinkedIn。最近收到过电子邮件的潜在客户,在 LinkedIn 上也处于冷却期。 |
| 退信的潜在客户 | 不受冷却期限制(仅限电子邮件——LinkedIn 不会退信)。检查冷却期时使用筛选条件:status=neq.bounced |
| 正在进行的营销活动中的潜在客户 | 始终不符合条件——如果潜在客户正在任何渠道的营销活动中,就不能将其添加到其他营销活动中 |
| 用户覆盖 | 用户可以明确要求对特定潜在客户覆盖冷却期——允许前请先征求确认 |
last_contacted 为空 | 从未联系过的潜在客户始终符合条件 |
输出目录
营销活动导出文件保存在:
skills/linkedin-outreach/output/
如果此目录不存在,请创建该目录。文件名格式为 {campaign-name}-{YYYY-MM-DD}.csv。