Respond to LinkedIn DMs with short, casual, human-like messages matching {{USER_NAME}}'s voice. Use for replying to thank-yous, compliments, interest expressions, and simple questions in LinkedIn messaging.
Triage and respond to LinkedIn DMs with short, lowercase, human-like messages matching {{USER_NAME}}'s casual texting voice.
Use Sonnet for this skill. The task is procedural (classify → execute DOM action) and doesn't need Opus-level reasoning. Sonnet at ~$0.27/DM vs Opus at ~$1.30/DM.
NEVER send a message without explicit user approval. Always:
NEVER reply if {{USER_NAME}}'s message is the most recent one in the conversation. The cadence must always be them→me→them→me. Never me→me. If {{USER_NAME}} already replied last, skip the conversation entirely. This is the single most important rule — double-messaging looks automated and not human.
{{USER_NAME}}'s LinkedIn DM voice:
Trigger: Someone expresses thanks, satisfaction, gratitude, compliments {{USER_NAME}}'s work/content, says something nice, or shares how their content helped them.
Action: Reply with a short, lowercase, warm acknowledgment. Generate contextually — read their actual message and respond to what they said, not a generic template.
Tone calibration examples (for style reference only, don't copy verbatim):
For longer/heartfelt messages, lean slightly longer but still casual:
Trigger: Someone confirms a lead magnet link worked, says "got it", bare "Thanks", "Yes", or sends a simple acknowledgment like 👍. Purely transactional, no emotional content.
Action: React with 👍 (thumbs up emoji reaction on their message). Do NOT send a text reply.
Important distinction: If their confirmation ALSO includes genuine gratitude or a compliment (e.g., "It works! Thanks, huge fan of your work"), use Category 1 instead. Category 2 is only for bare confirmations with zero emotional content.
Trigger: Any of:
Action: Mark the conversation as unread. {{USER_NAME}} will review these themselves.
LinkedIn's contenteditable does NOT work with type_text or fill. You MUST use evaluate_script:
Step 1 — Type the message:
() => {
const editor = document.querySelector('div[contenteditable="true"].msg-form__contenteditable');
if (!editor) return 'no editor';
editor.focus();
editor.innerHTML = '<p>YOUR MESSAGE HERE</p>';
editor.dispatchEvent(new Event('input', { bubbles: true }));
return 'typed';
}
Step 2 — Click send:
() => {
const btn = document.querySelector('.msg-form__send-button');
if (!btn) return 'no send button';
btn.click();
return 'sent';
}
Common mistake: Do NOT use selector .msg-form__contenteditable [contenteditable="true"] (descendant). The class is ON the contenteditable element itself: div[contenteditable="true"].msg-form__contenteditable.
JS mouseenter dispatch does NOT reveal the reaction toolbar. You MUST:
hover tool on the message text UID (with includeSnapshot: true to save a round-trip)menuitem "React with thumbs up" UIDMake sure you're hovering on the OTHER person's message, not {{USER_NAME}}'s.
In the thread, each message group has an a[href*="/in/"] profile link with text like "View [Name]'s profile". Check whether the last message group belongs to {{USER_NAME}} or the other person.
These are critical for keeping costs low:
Use includeSnapshot: true on click and hover calls whenever you need the page state afterward. This returns the snapshot in the same response — saves a separate take_snapshot call (1 round-trip + ~5K tokens per snapshot saved).
Minimize snapshots for mechanical actions. Cat 2 (👍) needs exactly 2 calls: hover with includeSnapshot → click menuitem. Cat 3 (mark unread) needs 2 calls: click options with includeSnapshot → click "Mark as unread". Don't take extra snapshots.
Don't re-read the conversation list between actions unless you need to find the next conversation. After finishing one conversation's action, just click the next conversation in the list directly.
Skip already-processed conversations. If the list preview shows "You: ..." that means {{USER_NAME}} replied last — skip without clicking in.
Process conversations ONE AT A TIME:
includeSnapshot: true)When asked to "dry run", do NOT type, send, react, or mark anything. Just present a table:
| # | From | Their Message | Category | Proposed Action |
|---|---|---|---|---|
| 1 | Name | "quote..." | 1 | Reply: "np man" |
| 2 | Name | "got it" | 2 | React 👍 |
| 3 | Name | "long pitch..." | 3 | Mark unread |
| 4 | Name | ({{USER_NAME}} replied last) | SKIP | No action (would double-msg) |
Process conversations ONE AT A TIME. For each:
Do NOT do two passes (scan all → compile table → execute all). That wastes tokens and degrades quality. Process each conversation fully before moving to the next. Present dry-run table only if explicitly asked for a dry run.
https://www.linkedin.com/messaging/?filter=unreadchrome-stealth MCP server (not chrome-devtools)