LinkedIn DM
by @10madh
Send personalized LinkedIn direct messages to a list of existing 1st-degree connections via browser automation. Use when the user wants to message LinkedIn connections with AI-personalized outreach β e.g. nurturing leads, following up after events, reconnecting with contacts, or announcing something. Takes a data file (CSV/TSV) or plain list with connection names and companies, asks for outreach context/goal, generates a tailored message per person, and sends each one via browser automation. Han
clawhub install linkedin-dmπ About This Skill
name: linkedin-dm description: Send personalized LinkedIn direct messages to a list of existing 1st-degree connections via browser automation. Use when the user wants to message LinkedIn connections with AI-personalized outreach β e.g. nurturing leads, following up after events, reconnecting with contacts, or announcing something. Takes a data file (CSV/TSV) or plain list with connection names and companies, asks for outreach context/goal, generates a tailored message per person, and sends each one via browser automation. Handles message compose flow, character limits, and incremental status tracking.
LinkedIn DM
Sends personalized LinkedIn messages to existing 1st-degree connections. Each message has:
β οΈ Pre-flight Checklist β Confirm Before Starting
1. Connection List
Ask the user for their data file or list. Must include (or be added):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:
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
profile="chrome"): extension attached to LinkedIn tab (badge ON) β recommended for flagged accountsprofile="openclaw"): openclaw-managed Chrome, LinkedIn logged in5. 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). Confirmgog 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: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?
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:
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
2. Wait 3β5 seconds
3. Search connections at linkedin.com/mynetwork/invite-connect/connections/ β type name in "Search by name"
4. 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)
6. Click Message button on their profile
7. Send Message 1 β personalized opener only, send it
8. Send Message 2 β pitch only, send it immediately after
9. Confirm both delivered
10. 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 |
|---|---|
| Sent | Message delivered this session |
| Already Messaged | 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-negotiableCRM 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. 2026-02-13 |
| 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 Β· Closed Lost
Appending a Row
After each message pair is sent, run:
gog sheets append "Outreach!A:L" \
--values-json '[["","","","","","","","","","Sent","",""]]' \
--insert INSERT_ROWS
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:
gog sheets update "Outreach!A1:L1" \
--values-json '[["Date Sent","Person Name","Role / Title","Company","LinkedIn URL","Relationship Hook","Opener Sent","Pitch Sent","Campaign","Status","Notes","Last Updated"]]' \
--input USER_ENTERED
Local Sidecar (fallback)
If Google Sheets is not set up, fall back to a local linkedin_dm_progress.json:
{
"campaign": "AI Calling - Feb 2026",
"pitch": "confirmed pitch text",
"rows": [
{
"date": "2026-02-13",
"name": "Shorya Saini",
"role": "Senior Analytics Specialist",
"company": "Razorpay",
"url": "https://linkedin.com/in/shorya-saini",
"hook": "Same batch BITS Goa 2018",
"opener": "Hey Shorya...",
"pitch": "I'm building...",
"status": "Sent",
"notes": ""
}
]
}