Agent skill
linkedin-outbound
Run a LinkedIn outbound loop with RouterGrowth on a LinkedIn account the user connected: search people (linkedin.search), read the profile before writing (linkedin.profile), send a connection request with a short note (linkedin.invite), message once accepted (linkedin.message), read and triage replies (events, linkedin.messages), inside daily caps.
Filed under Outbound email and LinkedIn and social.
From RouterGrowth/skills · 12 skill entries · 2 · pushed 2026-10-03
What it does when it runs
Run a LinkedIn outbound loop with RouterGrowth on a LinkedIn account the user connected: search people (linkedin.search), read the profile before writing (linkedin.profile), send a connection request with a short note (linkedin.invite), message once accepted (linkedin.message), read and triage replies (events, linkedin.messages), inside daily caps. Use when the user wants LinkedIn outreach, connection requests at scale with a human gate, wants to message people who accepted, asks "who replied on LinkedIn", or wants to work a LinkedIn list.
Automated analysis of the skill and the 0 files bundled beside it. A skill’s own description is written to be selected by an agent, so it describes the job and not the dependencies.
- Keys and connectors you must supply
- None found.
- Hosts it reaches
- www.linkedin.com
- www.routergrowth.com
- Tool permissions it declares
- No
allowed-toolsin the frontmatter. It does act, so it runs under whatever permissions your session already grants. - Actions present in the files
- shell
Install it
View source on GitHub ↗git clone --depth 1 --filter=blob:none --sparse https://github.com/RouterGrowth/skills.git /tmp/skills git -C /tmp/skills sparse-checkout set "skills/linkedin-outbound" mkdir -p ~/.claude/skills/linkedin-outbound cp -R "/tmp/skills/skills/linkedin-outbound/." ~/.claude/skills/linkedin-outbound/
Picked up without a restart. A project skill of the same name is shadowed by your personal one. For one repository only, swap ~/.claude/skills for .claude/skills. Claude Code docs ↗
Or take the whole library
This repo ships a .claude-plugin manifest, so Claude Code can install all 12 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.
/plugin marketplace add RouterGrowth/skills /plugin
The folder is the same in every client that implements the format — 46 of them — so if yours is not above, only the destination changes.
The skill
Source on GitHub ↗Reproduced in full from RouterGrowth/skills/blob/c9161f688c06967dbdeca865201100cf67a0741c/skills/linkedin-outbound/SKILL.md, which is licensed MIT (repository). 1,402 words, 11 headings.
LinkedIn outbound
One connected LinkedIn account, a target search, a daily budget of invites and messages, and a human who approves every batch of copy before it goes out. The agent does the search, the reading and the drafting; the invites and messages go out only after the gate.
Before you start
- Load the core
routergrowthskill (https://www.routergrowth.com/SKILL.md) if it is not loaded. Confirm access with the freebalancetool orroutergrowth balance. - Run the free
linkedin.accountsfirst. It lists the accounts the workspace already connected, which profile each one is, and theaccount_idto pass; ignore rows whose status isEXPIREDorPENDING(a sign-in link that was never finished). If none is connected, runlinkedin.accountwith a name: it returns a hosted sign-in link the user opens once. Connecting is free, but each connected account then rents a monthly seat charged to the wallet until it is disconnected (thelinkedin.accountcatalog note has the amount): tell the user before sending the link. Never ask for the user's LinkedIn password. - Ask for the target (title, company type, location), the offer in one sentence, and the daily caps if the user has them. Default: 20 actions a day per account, invites and messages together, never both to the same person on the same day. Keep the log at
linkedin/log.csv(columns: date, action, profile_url, provider_id, run_id, note_or_message, status) and the suppression list atlinkedin/suppressed.txt, relative to the folder the user names; ask once. - Inspect
linkedin.search,linkedin.profile,linkedin.invite,linkedin.messageonce, then quote the day: N searches, N profiles, N invites, N messages, plus the monthly seat if an account is being connected.
Steps
1. Search
routergrowth run -c linkedin.search -i '{"keywords":"head of growth fintech","location":["London"],"network_distance":[2,3],"account_id":"<id>","limit":10}' --max-cost 0.05 --wait 60 -o search.json
A search costs a fraction of a cent. location, company and industry take place or company names as well as LinkedIn ids; a name resolves to its first LinkedIn match ("London" becomes "London Area, United Kingdom"), so use the name LinkedIn shows for the place you mean. A name that matches nothing is dropped, the search still runs, and the result carries dropped_filters and a note: check for them before trusting the list, and fall back to a broader place name, the id, or the city in keywords. Classic search returns a page of 10 whatever the limit and bills the page; use cursor for the next one. Rows carry provider_id, public_identifier, profile_url, name, headline, location, degree: the company is in the headline, and location comes back in the account's own LinkedIn language ("Londres, Angleterre, Royaume-Uni" on a French account), so never filter rows on an English place string. Expect noise (a "Head of People" for a growth search, an anonymous "LinkedIn Member" row): the profile read in step 2 is what qualifies a row, not the search. Keep name, headline, profile URL. Before anything else run the free dedupe pass: routergrowth history --file urls.txt with the profile URLs and read the contacted column, which is who this workspace already invited or messaged; drop them. seen is always yes right after a search and means nothing here.
2. Read before writing
routergrowth run -c linkedin.profile -i '{"profile_url":"https://www.linkedin.com/in/example","full":true,"account_id":"<id>"}' --max-cost 0.10 --wait 60
The profile read returns provider_id, which linkedin.invite and linkedin.message accept in place of the URL. Without full: true it carries only the name, headline, location, degree and counts; full adds the summary, the work history and the skills (LinkedIn throttles it, so read at most the people you will write to). There is no posts field. Pull the one detail the note will use: a recent role change, a company event, a line from the summary. A note without a real detail is a template; do not send templates.
3. Draft the notes
One per person, under 300 characters (LinkedIn's cap; shorter reads better), no link, no pitch, one reason the connection makes sense. The offer sentence belongs in the step 5 message, not in the note. Put them in a table: name, profile URL, the detail used, the note.
Gate 1: show the table and wait. The user edits or approves. Nothing is sent before this.
4. Invite
routergrowth run -c linkedin.invite -i '{"profile_url":"https://www.linkedin.com/in/example","message":"<approved note>","account_id":"<id>"}' --max-cost 0.05 --wait 60
Before the batch, run the free headroom check:
routergrowth run -c linkedin.invitations_sent -i '{"account_id":"<id>"}' --wait 60
It counts the account's pending invitations over the last 24 hours and 7 days and returns remaining_today, the most to send now (LinkedIn allows roughly 100 a day and 200 a week). Send no more than the lower of remaining_today and the daily invite cap; when it is 0, send nothing and say so. The counts are a floor (accepted and withdrawn invitations leave the pending list), so the user's own cap still rules. Stop the batch at the first provider_rate_limited; it bills nothing and is never retried.
Up to the daily invite cap. Log each one: profile URL, date, run ID, note.
5. Message after accept
On the next run, start with the free events call. One call covers every connected account:
routergrowth events --types linkedin.message_received,linkedin.new_connection --cursor-file events-cursor.txt -o events.json
--cursor-file (CLI 0.6.0 or later) keeps the id of the last event seen next to the CSV log, so each run returns only what is new; with no cursor file yet, add --since <last run's timestamp>. Over MCP, the events tool takes the same types and an after cursor: the previous answer's next_after, which you log yourself.
linkedin.message_receivedis a reply:sender_name,sender_profile_url, thetext, and thechat_idto answer in.linkedin.new_connectionis a new connection:name,public_identifier,profile_url. Match it against the invite log; a match is an acceptance. It can arrive up to 8 hours after the person accepted, so an invited person with no event yet is still pending, not declined.
When there is no cursor yet, or the call fails, read the inbox instead: linkedin.messages with unread: true and after set to the last run's timestamp lists the conversations with something new, each with the person's name, profile_url, headline and degree; an empty list means nobody wrote, and is a success, not an error. Acceptance is not in the inbox: linkedin.profile on each invited person shows degree 1 (connected: true) once they accepted, which is also the check for an invitation older than a day with no linkedin.new_connection event. For accepted connections that have not replied, draft one message each: the offer in two sentences, one question, no attachment.
Gate 2: show the messages and wait. Then:
routergrowth run -c linkedin.message -i '{"profile_url":"https://www.linkedin.com/in/example","text":"<approved message>","account_id":"<id>"}' --max-cost 0.05 --wait 60
Up to the daily message cap. Use chat_id instead of profile_url when continuing an existing thread.
6. Triage replies
Read each conversation with chat_id: messages carry text, sent_by_me, sender_name, sender_profile_url and sent_at. For every reply: interested, not interested, wrong person, question, out of office. Draft a response for interested and question; the user sends it or approves it. Add not interested to linkedin/suppressed.txt. Never message someone who said no.
Rules
- Caps are per account per day and shared across invites and messages. Never exceed them, and start lower on a fresh account (5 to 10 a day for the first week).
- One note per person, written from that person's profile. No bulk template, no link in a connection note.
- The agent drafts; the user approves each batch. A scheduled run may search, read and draft on its own, and may send only copy the user approved in an earlier gate.
- Respect LinkedIn's terms and the user's: this skill sends through the user's own account on a hosted connection, at human pace, with a human in the loop. It never scrapes beyond what the capabilities return and never automates likes, comments or posts.
max_coston every run,--wait 60on every example (a barerunreturns a receipt, not a result). Every action logged with its run ID. Report the day's cost as the sum of every run'sbilling.charged, not a wallet difference: the balance is shared by every session on the key.
Output
A daily briefing: invites sent (with the log), acceptances since last run, messages sent, replies by class with the drafts to approve, and the running totals for the week. Plus the CSV log the next run reads.
Other skills for the same job
Different authors, same problem. Matched on the words in the skill name, across every library in the catalogue except this one.
- linkedin-outbound-angle by Othmane-Khadri · 317
- linkedin-marketing by sergebulaev · 4,230
- linkedin-comment-drafter by sergebulaev · 4,230
- linkedin-content-planner by sergebulaev · 4,230
- linkedin-employee-advocacy by sergebulaev · 4,230
- linkedin-engager-analytics by sergebulaev · 4,230
- linkedin-hook-extractor by sergebulaev · 4,230
- linkedin-humanizer by sergebulaev · 4,230
Need help setting it up?
This page tells you what linkedin-outbound does and what it needs. Cheetah builds the agent setup it runs inside: data, CRM, sequencing and the guardrails.
Book a call →The directory stays free. There is nothing gated behind this.