Skills / Outbound
Multichannel intent outbound
Five steps from signal to reply: high-intent leads, their LinkedIn, a connection note and a message, a verified email, then an email and a WhatsApp follow-up when nobody answers.
Five steps, one key, one balance, three channels the user already owns:
- Find high-intent leads. Only people with a dated, public reason to care this week.
- Get their LinkedIn. The profile is the identity everything else hangs on.
- Connect and message. A note written from the signal, a message once they accept.
- Enrich. A verified work email for everyone; a mobile only for the people who reach the last step.
- Follow up if no answer. Email on day 3, WhatsApp on day 5 or later, and any reply on any channel stops everything.
The agent does the finding, the reading and the drafting. Every send goes out from the user's own connected accounts, after a gate the user passes. This skill chains three others; read them for depth: hiring-signal-outbound and social-lead-discovery (step 1), linkedin-outbound (step 3), sdr-daily and docs/daily-lifecycle.md (the daily loop).
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, without printing credentials. - Check the channels with the free listings:
linkedin.accounts,gmail.accounts(oremail.inboxesfor a RouterGrowth inbox on a domain the user verified),whatsapp.accounts. Ignore rows whose status isEXPIREDorPENDING. A missing channel is connected withlinkedin.account,gmail.accountorwhatsapp.accountand aname: each returns a hosted sign-in link (Google OAuth, a WhatsApp QR or pairing code) the user opens once. Never ask for a password. Say that a connected Gmail or WhatsApp account is billed monthly (theinspectnote gives the amount) before connecting one. - Ask for: the signal (the role they hire for, the problem phrase they post about, or the competitor they engage with), the buyer (titles and seniority that sign), the geography, the offer in one sentence, and the daily caps. Defaults: 20 LinkedIn actions a day per account, 30 emails a day per mailbox, 10 WhatsApp messages a day, and the cadence day 0 LinkedIn, day 3 email, day 5 WhatsApp.
- Keep three files in the folder the user names (ask once):
outbound/leads.csv(one row per person, the columns under Output),outbound/log.csv(date, channel, action, person, run_id, text, status) andoutbound/suppressed.txt(anyone who said no, bounced or asked to stop, on every channel). - Inspect every capability the run will touch, once, and quote the batch before the first billable run: N leads, N profile reads, N invites, N email lookups and verifications, N phone lookups at most, N sends per channel. Get a yes on the total. A scheduled or delegated run treats the budget the caller set as the approval, stops at it and reports.
1. Find high-intent leads
Pick one signal source per run; the lead table records which one produced each row.
| The signal | How to get it | Then |
|---|---|---|
| Hiring for the role you sell to | company.jobs by title and location, company.search for the domain and headcount |
people.search with company_domains for the buyer (hiring-signal-outbound) |
| Posting about the problem | social.search on X, LinkedIn, Reddit, YouTube, TikTok or Instagram with a phrase a buyer would write |
read the post, keep the buyers, social.profile for the website (social-lead-discovery) |
| Engaging a competitor | social.comments on the competitor's recent posts |
the commenters who ask, compare or complain |
| Money to spend | company.funding on the accounts you already have |
the buyer at each, as for hiring |
routergrowth run -c people.search -i '{"company_domains":["acme.com"],"titles":["Founder","CEO","Head of Growth"],"seniority":["cxo","owner","vp","director"],"limit":2}' --max-cost 0.30 --wait 120
With a domain, people.search bills per person returned and carries the LinkedIn URL and, when known, a work email. Qualify by reading, not by keyword: drop vendors, recruiters, students, competitors and anyone who is not the buyer. Every kept row carries the signal text, its date and its URL. No signal, no lead. Then run the free dedupe: routergrowth history --file people.txt and drop everyone this workspace already contacted on any channel, plus everyone in suppressed.txt.
2. Get their LinkedIn
Rows from people.search already carry linkedin_url. For the rest (a lead from a post, a comment or a job ad):
routergrowth run -c linkedin.search -i '{"keywords":"<first> <last> <company>","account_id":"<id>","limit":10}' --max-cost 0.05 --wait 60
Accept a match only when the headline names the company or the role the signal showed; two plausible rows means no match. person.enrich with a work email is the second route when you already have one. A lead with no LinkedIn skips step 3 and starts on email in step 5.
Read the profile of everyone you will write to, with full: true (LinkedIn throttles full reads, so only them):
routergrowth run -c linkedin.profile -i '{"profile_url":"https://www.linkedin.com/in/example","full":true,"account_id":"<id>"}' --max-cost 0.05 --wait 60
Keep provider_id (the invite and message accept it), degree (1 means already connected: skip the invite, go to the message) and the one detail the note will use.
3. Connect and message
Draft one note per person, under 300 characters: the signal in their words, one reason to connect, no link, no pitch. The offer waits for the message.
Gate 1: show the table (person, signal, detail, note) and wait. Nothing is sent before the user approves it.
Check headroom with the free linkedin.invitations_sent and send no more than the lower of remaining_today and the daily cap:
routergrowth run -c linkedin.invite -i '{"provider_id":"<id>","message":"<approved note>","account_id":"<id>"}' --max-cost 0.05 --wait 60
Stop the batch at the first provider_rate_limited. Log every invite with its run ID. On later runs, linkedin.profile shows degree 1 once someone accepted: draft one message each (the offer in two sentences, one question, still anchored on the signal), Gate 2, then linkedin.message with the approved text. Details, caps and reply reading: linkedin-outbound.
4. Enrich
Enrich in the order the channels need it, so nobody pays for data a reply made useless.
- Work email, for everyone. Use the email
people.searchreturned, orcontact.findwith first name, last name and the company domain (a miss is released, not billed). Thencontact.verifyevery address before it touches a sender: keepvalid, dropinvalid, holdcatch_allandunknownin their own column and do not email them without the user's say.
routergrowth run -c contact.find -i '{"first_name":"Alex","last_name":"Rivera","company_domain":"acme.com"}' --max-cost 0.10
routergrowth run -c contact.verify -i '{"email":"alex.rivera@acme.com"}' --max-cost 0.02
- Mobile, only on day 5, only for the people still silent.
contact.phonewith the LinkedIn URL or the verified email; it bills only when a number is found and is the most expensive call in the loop, so never run it up front. Thenwhatsapp.profilewith the number: a number that is not on WhatsApp comes back as an unbilled no-match and ends the sequence for that person.
routergrowth run -c contact.phone -i '{"profile_url":"https://www.linkedin.com/in/example"}' --max-cost 0.30
routergrowth run -c whatsapp.profile -i '{"phone":"+14155550123","account_id":"<id>"}' --max-cost 0.02 --wait 60
5. Follow up if no answer
Every run starts by reading all three inboxes since the last run, before any new send: linkedin.messages with unread: true and after, gmail.messages with after (or email.messages for a RouterGrowth inbox), whatsapp.messages with unread: true and after. A reply on any channel ends every remaining step for that person on every channel. An inbox that cannot be read means unknown, not silence: hold that person's next step and say why.
| Day | Channel | When | Capability |
|---|---|---|---|
| 0 | invite with the note; message once accepted | linkedin.invite, linkedin.message |
|
| 3 | no reply on LinkedIn (accepted or still pending), verified email only | gmail.send or email.send |
|
| 5 or later | no reply to the email, a mobile found and confirmed on WhatsApp, and the user's policy allows it | whatsapp.message |
The email is short: a subject that names the signal, three sentences, one question, no attachment, a plain way to say no. The WhatsApp message is shorter: two lines, who you are and the one question, nothing that reads as a campaign. Ask the user once whether WhatsApp cold follow-ups are acceptable for their market and their own policy; many teams keep it for people who accepted on LinkedIn. Respect it for the whole run.
Gate 3: show every follow-up (person, channel, sender, text) and wait. An approval covers one exact message to one person on one channel; change any of them and it is void.
routergrowth run -c gmail.send -i '{"to":["alex.rivera@acme.com"],"subject":"<approved subject>","text":"<approved body>","account_id":"<id>"}' --max-cost 0.05 --wait 60
routergrowth run -c whatsapp.message -i '{"phone":"+14155550123","text":"<approved message>","account_id":"<id>"}' --max-cost 0.05 --wait 60
Reply in the same thread when one exists: in_reply_to with the message_id from gmail.messages, chat_id from whatsapp.messages or linkedin.messages.
Triage every reply: interested, question, not now, wrong person, not interested, out of office. Draft a response for interested and question for the user to approve. Not interested, a bounce or a request to stop goes into suppressed.txt and ends every channel for that person.
Rules
- Inspect once per capability, quote the batch, then run.
max_coston every run and--wait 60on every send example (a barerunreturns a receipt, not a result). Stop and ask before a batch over about $1 unless the user asked for that volume. - Nothing is sent without its gate. A scheduled run may search, read, enrich and draft on its own, and may send only copy the user approved for that exact person and step.
- One active sequence per person, one message per channel per step, never two channels on the same day. Caps count new sends only; reading inboxes is always complete.
- Never invent a last name, a LinkedIn match, an email or a number. Never email an unverified address. Never buy a phone number for someone who already replied.
- Start slow on fresh accounts (5 to 10 LinkedIn actions a day for the first week) and respect LinkedIn's, Google's and WhatsApp's own limits: the first
provider_rate_limitedstops that channel for the day. - Never present sandbox (
rg_test_) output as real data. Every row carries the run IDs behind it. Report cost as the sum of every run'sbilling.charged, not a wallet difference.
Output
outbound/leads.csv, one row per person: name, title, company, domain, signal, signal_date, signal_url, linkedin_url, provider_id, degree, email, verify_status, phone, on_whatsapp, stage, last_touch, next_touch, replied_on, run_ids, charge. stage is one of found, invited, connected, messaged, emailed, whatsapped, replied, suppressed.
A daily briefing: replies by channel with the drafts to approve, invites accepted, sends done per channel against the caps, follow-ups due tomorrow, people suppressed, and the day's total charged with the run IDs.
What a lead costs
Checked with inspect on 25 September 2026; run inspect again before quoting, prices move. A person found through people.search with a domain is $0.042 with the email when known; a LinkedIn profile read $0.002; an invite or a LinkedIn message $0.00875; contact.find $0.04375 per match and nothing on a miss; contact.verify $0.010938 a verdict; gmail.send and whatsapp.message $0.00875 a send; contact.phone $0.21875 per number found; whatsapp.profile $0.002. A lead worked to the email step costs about $0.12; one that goes all the way to WhatsApp about $0.35, most of it the phone lookup, which is why it runs last and only for the people still silent.