superagnt_

Ready? The setup guide gets it running in your agent in about 10 minutes.

Follow the setup guide→

LinkedIn Warm Outbound

This week's warm leads: the people engaging with posts about your problem, scored and staged with a first message each.

linkedin warm outbound · one run
live
2Pulls and scores every engager
1,048engagers

deduped at the source · ICP check runs before enrichment spends anything

MH
Maren Holt
GTM Engineer · Softline
qualified
icp fit89%
signalcommented on a churn teardown
emailverified
212
clear your icp
186
emails verified
836
skipped free
3Stages the outbound
64staged

HeyReach · Instantly · AgentMail · Gmail · into the one lane you picked

ASAvery StoneHeyReach

Saw you've been in a lot of the churn conversations lately. Most of it traces back to who the list was built from…

stagedHead of Growth · Fieldnote
IFInes FerreiraInstantly

You've been deep in the outbound tooling threads. Honest answer on tools: it depends on where your list comes from…

stagedRevOps Lead · Brightloom
TLTheo LindqvistGmail draft

Saw you've been in the AI SDR conversations. The sourcing side is the whole game, happy to show what we mean…

stagedDirector of Sales · Meridian
MHMaren HoltAgentMail

List quality keeps coming up in the threads you're in. Same scar here. We source from engagement now, want the breakdown?

stagedGTM Engineer · Softline
64 staged · campaign: stopped·starting it is yours

Your market tells you who's shopping, in public, every day: they react to and comment on posts about the problem. This agent finds those posts, pulls everyone on them, and scores each person against your ICP before it pays for a single email lookup. The ones who fit land in HeyReach, Instantly, AgentMail or your Gmail with a first message written from what they engaged with. You press start.

LinkedIn posts, reactions + commentsPeople + company enrichment (first-party)Email finder + verifier (first-party)Workspace PostgresHeyReach · Instantly · AgentMail · Gmail
01// the receipts

We run this on ourselves.

Our own LinkedIn account, eight weeks, this exact recipe. We never measured a before for this account, so there's no before-and-after here. The numbers are read straight off the dashboard.

Outbound dashboard for 1 July to 24 August 2026: 1,582 connections sent, 985 accepted (62.3%), 866 leads messaged, 312 replied (36%), 179 flagged interested, and a daily activity chart across the whole window.
»Our own LinkedIn outbound, 1 Jul to 24 Aug 2026. Same recipe this skill installs.
62.3%
connections accepted
36%
reply rate
179
flagged interested
1,582
requests sent · 8 weeks
02// step by step

Seven steps to your first warm lead list.

  1. 1

    Tell it who buys, once

    Four answers: who owns the problem and who looks close but never buys, the competitors and creators your buyers follow, who it should never contact, and the one lane you send from. It drafts all four from your website, so you only fix what's wrong.

    agnt_tools_enable · agnt_db_insert

    icp_profile · drafted from your site

    buyercategory + retail ops leads at CPG brands

    never4 competitors · 3 customers · your team

    watch2 competitors · 6 creators

    laneheyreach

    you correct it once

  2. 2

    Fish where buyers post

    Three ponds: posts from buyers who have the problem right now (hiring for it, breaking under it, shopping for a fix), the latest posts from your watchlist, and the topic itself. Searches use the words buyers post in, because vendor jargon only finds other vendors. No term repeats inside 14 days.

    data_linkedin_search_posts · data_linkedin_search_post_by_hashtag · data_linkedin_get_company_s_post · data_linkedin_get_profile_s_posts

    this run's searches, by pond
    buyers“hiring a category manager”40%
    watchlistRivalCo · 2 recent posts30%
    topic“shelf reset”30%

    » no term reused inside 14 days

  3. 3

    Pull everyone on the post

    The reactors, the commenters, and the author when a buyer wrote it. Each row keeps what that person did: the comment itself, or the reaction. Anyone already in your table is dropped at the source, and so are your competitors and their staff.

    data_linkedin_get_post_reactions · data_linkedin_get_profile_post_comment · agnt_db_upsert

    everyone on one post
    41 reactors · 12 commenters · 1 author

    » new · Dir. Category, Norrin · wrote the post

    » new · Head of Retail, Mesa Co · commented

    » new · VP Growth, Halloway · reacted

    » dropped · 27 already in your table · 3 competitors

  4. 4

    Score before you pay for an email

    A narrow gate drops fake profiles, the wrong region and your do-not-contact list. Everyone left gets one score from 0 to 100 against your ICP, with the reason on the row. Only leads over the bar get an email found and verified, and a LinkedIn-only lane skips that spend entirely.

    data_linkedin_get_profile_data_by_url · data_agnt_companies_enrich · data_agnt_people_email_finder · data_agnt_people_email_verifier

    26 new leads · the score gates the spend
    over 50 · email found + verified14
    under the bar · nothing spent9
    gated · wrong region3

    72 · owns category reviews at a 40-store brand, hiring for it

  5. 5

    Open on what they engaged with

    One message with one job: a reply. It opens on the topic they keep engaging with, never the creator whose post they were on, and asks for something small. It can also offer a free resource built from their own company's data. You approve the first five before anything is staged.

    agnt_db_upsert

    first touch · opener: topic

    “Saw you've been in a lot of the category review threads lately. We put together a shelf-gap breakdown for your top SKUs. Want it?”

    » the topic, never the creator · one small ask

    you approve the first five

  6. 6

    Stage it, never start it

    A HeyReach list, an Instantly campaign, AgentMail drafts or Gmail drafts, whichever lane you picked. Each lead carries its own message, and the campaign stays stopped. Pressing start is yours, and it's the last point where a bad list can still be caught.

    connection_heyreach_list_add_leads_v2 · connection_instantly_bulkaddleads · connection_agentmail_create_draft

    staged in the lane you picked
    heyreachinstantlyagentmailgmail

    » 14 leads on the campaign's list, each with its own message

    campaign stopped · you press start

  7. 7

    Let replies steer the next run

    Webhooks bring replies back onto the same rows, sorted by intent, with an answer drafted for you to approve. The next run writes more searches like the ones that got answers. Once a run looks right, the same steps go on a schedule as a small hosted agent team.

    agnt_webhooks_create_endpoint · connection_heyreach_webhook_create · connection_instantly_createwebhook · agnt_db_execute_sql

    replies, feeding the next run
    webhook · Mesa Co replied · interested

    » answer drafted · waiting on your ok

    » “category review” · 31 leads · 9 replies

    » “retail media network” · 22 leads · 0 replies

    next run writes more like the first

»After the first good run, the skill offers the always-on version: a small hosted runner on a schedule, so the work keeps happening with your laptop closed. You confirm the cadence before anything is scheduled.

03// what's inside

Read every file before you install it.

Plain markdown your agent reads while it runs. SKILL.md is the procedure, and references/ holds the 5 files it opens when a step needs more depth. This is the plugin exactly as it installs from github.com/superagnt/plugins: 8 files, 112 KB.

warm-outbound-engine/8 files · 112 KB
skills/
warm-outbound-engine/
SKILL.mdentry29 KB
references/
autopilot-agent-team.md18 KB
cold-outreach-playbook.md19 KB
free-resource-first-touch.md16 KB
icp-scoring-rubric.md12 KB
webhook-auto-reply.md16 KB
plugin.json741 B

+ pulls in superagnt on install

skills/warm-outbound-engine/SKILL.mdgithub
---
name: warm-outbound-engine
description: This skill should be used when the user wants to run the LinkedIn Warm Outbound skill, or asks to "scrape LinkedIn post engagers", "build outbound from people engaging with my market", "find warm leads on LinkedIn", "turn LinkedIn engagement into a sequence", "write outreach from what people commented", "offer each lead a free personalized resource", "auto-reply to LinkedIn or cold email replies", or "put my LinkedIn outbound on autopilot", or wants recurring engagement-sourced prospecting staged into HeyReach, Instantly, AgentMail or their own Gmail. Runs on the superagnt MCP: LinkedIn post + engager data, first-party enrichment, workspace database, and one sending lane of the user's choice, with a research-backed cold outreach library and a recurring version that runs daily or every other day. Stages campaigns stopped; starting a sequence is always the human's.
version: 0.3.1
---
# LinkedIn Warm Outbound
Outbound sourced from engagement, not from a list. Every lead in the table
did something in public first: reacted to, or commented on, a post about the
problem you solve. Builds on the base `lead-generation` skill for the
enrichment mechanics — read that for per-tool detail; this one adds the
engagement source, the scoring gate, the first message, and the four sending
lanes.
The intended arc: run it once end to end in this client, look at what it
staged, then put it on autopilot: the same sweep daily or every other day,
on this client's own scheduler or a deployed superagnt agent (step 7).
## The outreach library
This skill ships a reference library in `references/`, researched September
2026 from full YouTube transcripts (Alex Hormozi, Nick Abraham, Alex
Berman), Nick Abraham's LinkedIn outreach posts on X, and agnt_'s own
production system. Read the matching file before the step that needs it,
and prefer it over generic outreach folklore.
- `references/icp-scoring-rubric.md`: the two-layer scorer (a narrow gate,
then one holistic score against the ICP description) and the weekly
calibration loop. Read before step 4.
- `references/cold-outreach-playbook.md`: targeting, connection requests,
first messages, follow-ups, replies, sending limits and benchmarks, per
lane. Read before step 5.
- `references/free-resource-first-touch.md`: build each qualified lead a
personalized asset from their own data and offer it instead of a pitch.
Read when step 1 set a `first_touch_asset`.
- `references/webhook-auto-reply.md`: vendor webhooks into a deployed agent
that classifies replies, logs them, and drafts or sends answers under
approval.
- `references/autopilot-agent-team.md`: the deployed version of step 7, a
recurring agent team on superagnt: the team shape, data jobs for the
per-lead work, cadence, cost, and how real deployments broke.
For LinkedIn via HeyReach, also load the platform's own doctrine:
`agnt_guidance_load({ "name": "linkedin-outreach" })`.
No `references/` folder next to this file (a harness that saved only
SKILL.md)? Fetch the one you need from
`https://raw.githubusercontent.com/superagnt/plugins/main/plugins/warm-outbound-engine/skills/warm-outbound-engine/references/<file>`.
If that fails, say so once and proceed: the steps below stand alone.
## Step 0 — tools
Codex: superagnt's tools can take a few seconds to appear in a new chat.
If none are listed, wait about 10 seconds and check again before concluding
superagnt isn't connected or falling back to web search.
`agnt_tools_list_enabled`; if missing on a flat server:
`agnt_tools_enable({ "families": ["data:linkedin", "database"] })` (the
first-party `data_agnt_*` people/company tools are always on). A grouped
server already serves every data source and the database: nothing to enable.
That is the whole first run — do not enable a sending lane yet.
`requires_upgrade` → `confirm_url` to the user → wait → re-run. (The
`warm-outbound-engine` skill endpoint pre-enables these two.)
### Tool names on grouped servers
This skill writes flat (per-operation) names. New servers list grouped tools
instead: one tool per resource with an `action` argument. Flat names still
resolve there but are not listed, so most clients will not let you call
them. The calls this skill makes:
| Flat | Grouped tool, action |
|---|---|
| `data_linkedin_search_posts` / `_search_post_by_hashtag` | `data_linkedin_search`: `posts` / `hashtag_posts` |
| `data_linkedin_get_company_s_post` | `data_linkedin_companies`: `posts` |
| `data_linkedin_get_profile_s_posts` | `data_linkedin_activity`: `posts` |
| `data_linkedin_get_post_reactions` | `data_linkedin_posts`: `reactions` |
| `data_linkedin_get_profile_post_comment` | `data_linkedin_posts`: `comments` |
| `data_linkedin_get_profile_data_by_url` | `data_linkedin_profiles`: `profile_by_url` |
| `data_linkedin_get_company_by_domain` | `data_linkedin_companies`: `by_domain` |
| `data_agnt_companies_enrich` | `data_agnt_companies`: `enrich` |
| `data_agnt_people_email_finder` / `_email_verifier` | `data_agnt_contacts`: `find_email` / `verify_email` |
| `agnt_db_select` | `agnt_db_read`: `select` |
| `agnt_db_insert` / `agnt_db_upsert` | `agnt_db_write`: `insert` / `upsert` |
| `agnt_db_execute_sql` | `agnt_db_sql`: `execute_sql` |
| `agnt_tools_enable` | `agnt_tools_write`: `enable` |
| `agnt_guidance_load` | `agnt_guidance_read`: `load` |
| `connection_heyreach_campaign_get_by_id` / `_get_sequence` | `connection_heyreach_campaigns_read`: `get` / `sequence` |
| `connection_heyreach_list_add_leads_v2` | `connection_heyreach_list_members_write`: `add_leads` |
| `connection_heyreach_campaign_add_leads_v2` | `connection_heyreach_campaign_leads_write`: `add` |
| `connection_heyreach_campaign_start` | `connection_heyreach_campaigns_write`: `start` |
| `connection_instantly_listcampaign` | `connection_instantly_campaigns_read`: `list` |
| `connection_instantly_bulkaddleads` | `connection_instantly_leads_write`: `add` |
| `connection_instantly_activatecampaign` | `connection_instantly_campaigns_write`: `activate` |
| `connection_agentmail_create_inbox` | `connection_agentmail_inboxes_write`: `create` |
| `connection_agentmail_create_draft` | `connection_agentmail_drafts_write`: `create` |
| `connection_agentmail_send_draft` / `_reply_to_message` | `connection_agentmail_messages_send`: `send_draft` / `reply` |
| `connection_agentmail_list_messages` | `connection_agentmail_messages_read`: `list` |
| `connection_gmail_save_draft` | `connection_gmail_mail_write`: `save_draft` |
| `connection_gmail_send_email` | `connection_gmail_mail_send`: `send` |
`agnt_tools_list_enabled` keeps its name on both surfaces. `connection_*` tools
are grouped the same way as the rest; the lane calls are in the table.
**Grouped data calls return a markdown table that drops fields this skill
keys on.** Pass `response_format: "json"` on every LinkedIn call in steps 3
and 4: you get every field of the view instead of the default columns. Then
posts carry `urn` (the comments call takes it) and `url`; reactions carry
`urn` (the member id for `linkedin_member_urn`) and `url` (the reactor's
profile); comments carry `author_url` (the commenter's profile) and `url`,
which is the link to the comment itself, never a lead's profile. For any
field the view does not carry, such as a post author's profile URL, pass
`response_format: "raw"`: the full payload, exactly what the flat tool
returns.
## Step 1 — the ICP, and the searches it implies (first run only)
Ask once, in one message, and draft what you can from their website so they
only have to correct it:
- who buys, in their words: the roles that own the problem, the kind of
company, the situation in which they feel it, and who looks close but
never buys. This becomes the ICP description the score reads
(`references/icp-scoring-rubric.md`);
- where they can sell, and who never to contact: competitors, customers,
partners, their own team;
- a watchlist: the competitors whose posts their buyers react to, and 5 to
10 creators whose audience is the buyer. Optional, and the steadiest
source there is;
- which channel they send from: HeyReach, Instantly, AgentMail or Gmail
(recorded now, enabled at step 6);
- optionally, what they could give a prospect for free that proves they are
good at their job (a report, a teardown, a sample list, a working setup of
their product). See `references/free-resource-first-touch.md`; if they
have nothing, skip it and the plain opener still works.
Create the tables. If they exist from an earlier version of this skill, add
the missing columns instead of recreating them:
```
icp_profile(id, sells text, problem text, icp_description text,
trigger_signals text, target_geography text, personas text,
do_not_contact text, qualify_threshold int default 50,
lane text, first_touch_asset text, updated_at timestamptz)
watchlist(linkedin_url unique, kind text, name text, added_at timestamptz)
leads(id, person, linkedin_url unique, linkedin_member_urn unique, title,
company, company_domain, email, email_verified boolean,
source_kind text, pond text, source_post_url, engagement text,
engagement_text text, persona text, fit_score int, fit_reason text,
opener_variant text, first_message text, status text,
staged_at timestamptz, replied_at timestamptz, reply_intent text,
updated_at timestamptz)
search_history(term, kind, pond, searched_at default now())
scraped_posts(post_url unique, post_urn, pond, term_used, author, snippet,
scraped_at)
```
Then write the answers to `icp_profile` (`agnt_db_insert`) and the watchlist
(`kind` is `competitor` or `creator`). The do-not-contact list goes in before
the first scrape: engager lists are full of competitors, and messaging one
is the fastest way to get your outreach posted about.
Upsert key on `leads` is `linkedin_url` — it is the only identifier that
survives a job change. LinkedIn also spells the same person as an encoded
member id (`ACoA…`) in some payloads, so keep `linkedin_member_urn` too and
dedupe on both. On a grouped server the default markdown view leaves out
the member id and a commenter's profile URL: request
`response_format: "json"` (see Step 0).
## Step 2 — choose this run's sources, then write its searches
Where you fish matters more than how hard. There are three ponds, and
only one of them is the obvious one:
1. Buyer behavior (write these first). Posts written by people who have
the problem right now: hiring for the function ("hiring a [role that owns
the problem]"), scaling or straining ("our [process] is breaking"),
evaluating ("anyone using a [category] tool for [job]?"), or the pain
stated plainly at the end of a bad week. On these posts **the author is a
lead too**, and the strongest one there is: writing the post is the
intent signal.
2. The watchlist. The recent posts of the competitors and creators in
`watchlist`. Everyone reacting to a competitor's product content is
already shopping; a creator's audience is the buyer by construction.
3. Topic. Keywords and hashtags for the space. The widest pond and the
noisiest: people who talk about a category for a living are often the
ones selling into it, which is why it is one pond of three.
Over a week, aim for roughly 40% buyer-behavior searches, 30% watchlist and
30% topic. A run gives its searches to one or two ponds rather than all
three shallowly; the first run covers all three.
Do not keep a fixed keyword menu. Generate 3–6 fresh searches per run
from the ICP description, steered by two queries. Run both with
`agnt_db_execute_sql`; `agnt_db_select` can't express a join or a
`now() - interval` filter:
```sql
-- what has actually converted, per pond and term
SELECT sp.pond, sp.term_used, count(l.id) AS leads,
count(*) FILTER (WHERE l.fit_score > 50) AS qualified,
count(*) FILTER (WHERE l.replied_at IS NOT NULL) AS replied
FROM scraped_posts sp JOIN leads l ON l.source_post_url = sp.post_url
GROUP BY 1, 2 ORDER BY replied DESC, qualified DESC;
-- what not to repeat yet
SELECT term FROM search_history WHERE searched_at > now() - interval '14 days';
```
Write more phrases like the families with replies; drop the phrasing
patterns that produced leads and no replies. Never reuse a term from the
14-day window; everything older is fair game again. Give the next run's
searches to the pond that is producing.
**The single highest-leverage rule here: write the language your BUYER
posts in, not the language your industry sells in.** Category and execution
terms ("shelf reset", "category review", "Q3 launch at Target") surface
posts whose engagers are buyers. Vendor/martech terms ("retail media
network", "martech stack") surface posts engaged with by other vendors, and
a run of those poisons the table with people who will never buy. Test every
generated term: *would a buyer plausibly write or react to a post using this
phrase?*
Insert each term into `search_history` with its `kind` (`keyword`,
`hashtag`, `watchlist`) and `pond` as soon as its search call returns,
zero results included, so a run that dies halfway keeps the history of what
it actually searched. A term whose call failed (credits, provider error) is
not recorded and stays eligible next run.
## Step 3 — the sweep
1. Find the candidate posts: `data_linkedin_search_posts` for keyword
searches (buyer behavior and topic), `data_linkedin_search_post_by_hashtag`
for hashtags (topic only), and for a watchlist entry
`data_linkedin_get_company_s_post` (a company page) or
`data_linkedin_get_profile_s_posts` (a person), taking its 1–2 most
engaged posts from the last couple of months. 3–5 posts per run across
all ponds — that cap is deliberate, it is the run's whole data cost.
2. Prefer posts with real engagement; a post with 4 reactions is not worth
a scrape when a 90-reaction one is in the same result set.
3. Skip posts already in `scraped_posts`. Record the rest, with their
`pond`, as you take them.
4. `data_linkedin_get_post_reactions` for reactors,
`data_linkedin_get_profile_post_comment` for commenters (grouped:
`data_linkedin_posts` actions `reactions` and `comments`, with
`response_format: "json"`). Take a reactor's profile from `url` and
member id from `urn`; take a commenter's profile from `author_url`, never
from the comment's `url`. Record
`engagement` ('reacted' | 'commented') per lead — commenters convert
better and are worth a different opener. Keep what they did in
`engagement_text`: the comment itself for commenters, the reaction type
for reactors. It is the one personalization hook every lead comes with.
5. On a buyer-behavior post written by a person, add the author as a
lead too (`source_kind = 'author'`, with the post itself as
`engagement_text`); everyone else is `source_kind = 'engager'`. Never add
a competitor as the author of its own post, and drop the employees of a
watched company when you pull its engagers.
6. Drop anyone whose `linkedin_url` or `linkedin_member_urn` is already in
`leads`. Dedup at the source, never at send time. A grouped call's
default markdown table has no member id, and on comments its `url` is the
comment link, so a dedupe against it silently lets duplicates through:
fetch the json view first.
**Geography and other filters belong on the enriched profile, not on the
search payload.** LinkedIn's post-search results omit location for plenty of
genuinely in-market posters; filtering there silently starves the funnel.
Take the lead, filter it in step 4.
## Step 4 — score, then enrich
Two layers, in this order, from `references/icp-scoring-rubric.md` (read it
before the first scoring pass). **The score gates the spend: a lead that
fails it never costs an email lookup.**
1. Check the profile URL first (free): a missing or malformed one never
becomes a paid call.
2. `data_linkedin_get_profile_data_by_url` → real title, company, location,
and the canonical profile URL. Dedupe again on both keys.
3. The gate: reject only an explicitly out-of-region location or an
empty or fake profile (`fit_score = 3`, with the reason), and drop anyone
on the do-not-contact list. Never judge the role here: a gate that
guesses at fit from a headline starves the funnel.
4. `data_agnt_companies_enrich` (or `data_linkedin_get_company_by_domain`)
→ domain, size, industry. Research the company further only when the
score can't be settled without it.
5. The score: one holistic judgment against
`icp_profile.icp_description`: `fit_score` 0–100, a two- or
three-sentence `fit_reason` that cites the evidence and names the
uncertainty, and a `persona` only when confident. Above the bar
(`qualify_threshold`, default 50) qualifies, and an unresolved maybe
qualifies; a score at or under it needs a concrete wrong-way reason.
Below the bar → `status = 'disqualified'` and stop: no email lookup, no
message, ever.
6. Only for leads that cleared the bar, and only when the lane sends email:
`data_agnt_people_email_finder` from name + domain, then always
`data_agnt_people_email_verifier`. LinkedIn URL → email is not a one-step
lookup. Unverified stays `email_verified = false`; those rows are still
workable on LinkedIn. When `icp_profile.lane` is HeyReach, skip both calls.
7. `agnt_db_upsert` with `status` moving new → scored → enriched → staged,
so a failed run resumes instead of restarting. `enriched` means ready to
message. Never overwrite a known value with a blank.
Above ~500 leads a run, this belongs in a data job rather than a longer
session — one cheap structured call per lead
(`references/autopilot-agent-team.md`).
## Step 5 — write the first touch
Read `references/cold-outreach-playbook.md` before writing a single
message; it has the rules per lane. The ones that matter most:
- The message has one job: a reply. It reads one-to-one. No company
intro, no feature list, no calendar link in the first touch.
- Open with the topic they engage with, never the creator. "Saw you've
been in a lot of conversations about <topic>" works; "saw you liked
<creator>'s post" reads as surveillance and gets screenshotted back to the
creator. For commenters, the substance of what they said
(`engagement_text`) is fair game, in your own words; never quote it back
or link the post. For authors (`source_kind = 'author'`), their own post
is the natural opener ("saw you're hiring your first SDRs"): it's their
public statement, not someone else's audience. A generic compliment is
worse than nothing.
- Give before you ask, then ask small. A yes/no question or permission
to send something useful, not a meeting.
- Never invent a detail or a number. Exact figures from their own data,
or none at all.
Write each lead's message into `leads.first_message` and tag
`opener_variant` (`plain`, or `asset` when step 1 set a
`first_touch_asset`). With an asset, build it for the top-scored leads first
and offer it in the message: `references/free-resource-first-touch.md`.
Show the user the first five messages (and the first five assets) before
anything is staged. After they approve the style, show a sample each run.
## Step 6 — stage into the lane, once
Enable only the lane recorded in `icp_profile.lane`. Four lanes:
### HeyReach — LinkedIn sequences
`agnt_tools_enable({ "families": ["connection:heyreach"] })`, then load the
platform's HeyReach doctrine once:
`agnt_guidance_load({ "name": "linkedin-outreach" })`. The campaign engine
owns delivery (timing, acceptance, reply-stop); this skill owns judgment
(who, what to say, what to do with replies).
1. The user builds the campaign in HeyReach: the sequence, a sender
account, created with "Create empty list". Read it before touching it:
`connection_heyreach_campaign_get_by_id` for the status,
`connection_heyreach_campaign_get_sequence` for the variables each step
uses.
2. Personalization rides on the lead at enrollment and can't be changed
afterwards. Pass the whole first message as one custom variable
(`customUserFields: [{ "name": "fullMessage", "value": ... }]`) and make
the sequence's message step just that variable; also fill every other
variable the sequence references, plus `firstName`, `lastName`,
`companyName` and `position`. A missing variable falls back to generic
copy silently. Write the message to the row, read it back, then enroll.
3. Stage without starting: `connection_heyreach_list_add_leads_v2` into
the campaign's lead list works while the campaign is paused, and nothing
sends until the user resumes it. `connection_heyreach_campaign_add_leads_v2`
only works on an active campaign and starts delivery for those leads, so
use it only when the user has said the campaign is live and wants leads
flowing into it.
4. Leads already in HeyReach are skipped silently: a successful call does
not mean every lead was added. Stamp `staged_at` only on rows you can see
in the list afterwards.
Two settings that decide whether this works: **send a blank connection
request** (a request carrying a pitch converts materially worse than an
empty one), and turn on exclude-in-other-campaigns so one person never
appears in two sequences.
### Instantly — email sequences
`agnt_tools_enable({ "families": ["connection:instantly"] })`
`connection_instantly_listcampaign` to pick the persona-matched campaign,
then read its first step to see which variable it expects.
`connection_instantly_bulkaddleads` with `campaign_id`, the verified-email
rows only (an unverified address in an Instantly campaign is a bounce
charged against the sending domain), and `skip_if_in_workspace: true`. The
first message rides in the lead's `personalization` field or a
`custom_variables` key that the step references.
### AgentMail — an inbox the agent owns
`agnt_tools_enable({ "families": ["connection:agentmail"] })`
`connection_agentmail_create_inbox` once (its own domain — never the user's
primary), then `connection_agentmail_create_draft` per lead (`to`,
`subject`, `text`; never `send_at`) and `connection_agentmail_send_draft`
when the user releases them. Replies come back through
`connection_agentmail_list_messages` /
`connection_agentmail_reply_to_message`, which is what makes this lane the
one where the agent can actually hold the thread.
### Gmail — the user's own mailbox
Nothing to enable: a connected mailbox is already on. Once the user has
connected Gmail, `connection_gmail_*` is served on the workspace MCP like
any connected vendor; `agnt_tools_enable` cannot connect it. Not connected
yet → ask the user to connect Gmail on their superagnt connections page,
then re-run `agnt_tools_list_enabled`.
`connection_gmail_save_draft` per lead (`to`, `subject`, `body` as HTML)
puts each first message in the user's Drafts, where they review and send it
themselves. That is the default. `connection_gmail_send_email` sends for real
from the user's own address, gated only by their client's tool approval, so
call it only on an explicit instruction for that run. For the standing
version (step 7), wire the same ops onto the deployed agent as flat `tools`
entries: `{ kind: "integration", provider_id: "gmail", operation_id:
"save_draft" }` and `"send_email"`, where every send surfaces for approval.
In every lane: stage, do not start. Add the leads, leave the campaign
stopped, and report what is waiting. Pressing start is the user's act, and
it is the last point at which a bad list can still be caught.
## Step 7 — put it on autopilot
One batch is not the product. A list built once and never refilled goes
quiet within weeks: the sequence outruns the leads and the channel looks
dead. Once a run has worked end to end and the user liked what it staged,
offer the standing version: the same sweep (steps 2 to 6), run daily or
every other day. Smaller, more frequent runs keep the leads fresh (people
who engaged in the last day or two are the warm ones), keep each run cheap,
and keep the sending account under its limits.
There are two ways to run it. Offer whichever fits the user:
- This client's own scheduler. If the client can schedule a recurring
task whose runs have the superagnt tools, set the sweep up there. Each
run starts with no chat history, which is fine: the ICP, the leads and
the search history are in the workspace database, and the run reads them
like step 2 does. Write the task so a fresh session can do it alone: run
this skill's sweep for the stored ICP, the lane, the per-run cap, stage
and never start, then report. Runs use the user's plan for this client;
data calls still spend superagnt data credits. A scheduled run starts with
nobody signed in, so check two things before promising it: the client can
run unattended on this machine (its own sign-in, such as a long-lived token
for Claude Code, or an API key or ChatGPT login for Codex), and something
will fire it (the client's own scheduler, cron or launchd). Test one
unattended run. Every run reads strangers' posts and comments, nobody is
there to approve a tool call, and superagnt doesn't gate tools for a
client. So give the scheduled run an allowlist in its client permissions,
not a deny list: the `agnt_*` and `data_*` tools in the table under
Step 0, the lane's read tool, and its one staging call:
`connection_heyreach_list_members_write` (HeyReach),
`connection_instantly_leads_write` (Instantly) or
`connection_gmail_mail_write` (Gmail drafts). Nothing that sends or starts
goes on it: not `connection_gmail_mail_send`,
`connection_agentmail_messages_send`, `connection_heyreach_inbox_send`,
`connection_heyreach_campaigns_write`,
`connection_heyreach_campaign_leads_write` (its add can resume a paused
campaign), `connection_instantly_emails_send` or
`connection_instantly_campaigns_write`, nor their flat names on a
per-operation server. The AgentMail lane stays out of this route: an
AgentMail draft created with `send_at` sends itself, and no permission
stops that, so run it attended or on the deployed agent. In the test run,
check the scheduled run's tool list: none of those tools may appear. If the
client can't restrict a scheduled run's tools, use the deployed agent. Have each run report into a table
or canvas the user reads, not only a log, and have the next attended
session check the last run's `searched_at` so a dead schedule gets
noticed. If you are not sure the client can schedule, or that its
scheduled runs see the superagnt tools, say so and use the other way.
- A deployed superagnt agent on a schedule. For when the client can't
schedule, runs must happen with the user's machine off, replies should be
answered as they arrive, or the volume calls for splitting the work.
`references/autopilot-agent-team.md` covers the team shape (who does the
judgment, what moves into a data job to cut AI cost, what webhooks
trigger) and the ways real deployments of this exact motion have broken.
Agent runs spend AI credits.
The reply side is `references/webhook-auto-reply.md` either way: without it
`leads.replied_at` stays empty, step 2's steering query has nothing to learn
from, and the engine generates volume without ever getting warmer.
Confirm the cadence and the expected spend with the user before scheduling
or deploying anything, and hand over the `confirm_url` on any
`requires_upgrade` (a plan limit, spent data credits, or a trial that
ended).
## Step 8 — report
From the table, never from memory (`agnt_db_select`): posts scraped, new
leads, how many cleared the score bar, how many staged and into what, and
the reply rate per search-term family, per pond and per `opener_variant`.
Every report carries the score distribution line ("scored N: X qualified, Y
under the bar, Z gated"), which tells thin sourcing apart from a bar set too
high; when nothing qualified, list the five closest calls with their reasons
instead of an empty table. Once replies flow back: replies by intent and the
median time to answer one. Once a week, run the calibration loop in
`references/icp-scoring-rubric.md` with the user.
Name the terms that earned replies and the terms that did not — that
paragraph is what the next run reads.
## Hard rules
- Stage, never start. No campaign is started, activated or resumed by
this skill. `connection_heyreach_campaign_start` and
`connection_instantly_activatecampaign` are the user's to call.
- **Never send from the user's own mailbox without an explicit instruction
for that run.** Drafts are the default in the Gmail and AgentMail lanes.
- Replies to humans are approved. A deployed reply agent sends only what
the user approved, unless the user explicitly relaxed a named reply class
(`references/webhook-auto-reply.md`).
- One lane. Enabling all four loads hundreds of tools into the context
for no gain, and staging the same person into two channels is how a warm
lead becomes a complaint.
- Never invent. No made-up personalization, numbers or claims about a
lead's business, in a message or an asset.
- The do-not-contact list is absolute. Nobody on it is looked up,
staged or messaged, ever. A lead under the score bar never costs an email
lookup, an asset or a message.
- Never scrape a post's engagers twice, and never contact a `linkedin_url`
already in `leads`.
<!-- skill_id: warm-outbound-engine · source: https://github.com/superagnt/plugins -->
run it yourself

Set it up in 10 minutes

Pick your agent and paste one prompt. Free, no card.

or skip the setup

We set it up for you, free

We build it on our agents, hook it to your LinkedIn, and it sends every day. $0 until your first messages go out.