Agent skill
outreach
Universal B2B outreach skill.
Filed under Outbound email.
From emelia-io/claude-outreach · 17 skills · 12 · pushed 2026-09-09
What it does when it runs
Universal B2B outreach skill. Define an ICP, build and clean a lead list, find and verify professional emails and mobile numbers, write and personalize a multichannel sequence, check deliverability, launch the campaign, handle replies, and read the results. Works with the Emelia MCP server or REST API, the Basile API for French B2B data, or a plain CSV. Triggers on: outreach, cold email, prospecting, lead list, lead generation, email finder, email verification, phone finder, sequence, campaign, follow-up, deliverability, warmup, SDR, ICP, personalization, reply handling.
Read from 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
- BASILE_API_KEY
- EMELIA_API_KEY
- Hosts it reaches
- api.emelia.io
- app.emelia.io
- docs.basile.cc
- mcp.emelia.io
- 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
- shellwrites files
Install it
View source on GitHub ↗git clone --depth 1 --filter=blob:none --sparse https://github.com/emelia-io/claude-outreach.git /tmp/claude-outreach git -C /tmp/claude-outreach sparse-checkout set "skills/outreach" mkdir -p ~/.claude/skills/outreach cp -R "/tmp/claude-outreach/skills/outreach/." ~/.claude/skills/outreach/
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 17 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.
/plugin marketplace add emelia-io/claude-outreach /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.
Before you install: this skill will not complete its job on a bare agent. It needs BASILE_API_KEY, EMELIA_API_KEY, which you have to obtain separately.
The skill
Source on GitHub ↗Reproduced in full from emelia-io/claude-outreach/blob/585061e4d78fe14a70701fbc5aec5d1eeb3d580a/skills/outreach/SKILL.md, which is licensed MIT (skill frontmatter). 2,894 words, 17 headings.
Claude for B2B Outreach
Invocation: /outreach $1 $2 where $1 is the command and $2 is the target
(a brief, a list name, a CSV path, a campaign name or a URL).
This skill orchestrates 17 sub-skills and 5 sub-agents that take an outbound campaign from a vague idea to replies in an inbox. Every step writes a file you can read, edit and re-run. Nothing is a black box.
Quick reference
| Command | What it does |
|---|---|
/outreach pilot | Start here. A guided first campaign on 100 contacts, with a checkpoint at every step |
/outreach full <brief> | The complete pipeline: ICP, list, enrichment, copy, sequence, launch, analysis |
/outreach offer <url or description> | Turn what you sell into an offer a competitor could not copy, from your website or from a conversation |
/outreach icp <brief> | Turn a vague brief into a targeting spec: titles, industries, size, geography, signals |
/outreach leads <icp or query> | Build the lead list from Basile (France), LinkedIn or a CSV |
/outreach filter <list> | Deduplicate, exclude customers and blacklist, segment, drop unusable rows |
/outreach find-email <list> | Find professional emails, in bulk or one by one |
/outreach find-phone <list> | Find direct mobile numbers |
/outreach verify <list> | Verify deliverability of every address before sending |
/outreach enrich <list> | The full waterfall: find, verify, decide, fill custom fields |
/outreach write <angle> | Write the sequence copy: subjects, openers, follow-ups, break-up |
/outreach personalize <list> | Per-contact icebreakers and AI variables at scale |
/outreach sequence <spec> | Design the multichannel flow: steps, delays, conditions, A/B tests |
/outreach deliverability | SPF, DKIM, DMARC, warmup, sending limits, ramp plan |
/outreach campaign <spec> | Create and launch the campaign in Emelia |
/outreach replies | Triage replies, draft answers, handle unsubscribes |
/outreach audit <campaign> | Rates per step and per variant, against benchmarks, what to change |
/outreach update | Say whether this checkout is behind the published repository, and what changed |
No command given? Ask what the user wants to do, then suggest /outreach pilot
if they have never run a campaign, or /outreach full if they know their market.
The working directory
Everything a run produces lands in outreach/ in the current directory, so the user
owns the output and can re-run a single step without redoing the rest:
outreach/
offer.md # what you sell, and the block the writing prompt consumes
icp.json # the targeting spec, human editable
leads.csv # the list, one row per contact, source column kept
enrichment.json # what was found, what was not, what it cost
sequence.md # the copy, step by step, with variables
campaign.json # what was created in Emelia, with ids
audit.md # what is wrong with the emails, and the one thing to fix
Never overwrite a file from a previous run without saying so. Append a suffix when
the user is clearly starting a second campaign (leads-q4.csv).
Data sources
Three ways to get contacts, in this order of preference:
- Basile API (
api.basile.cc) for French B2B: around 26.8 million companies and 29.5 million contacts, from the legal registry, LinkedIn and Google My Business. HeaderAuthorization: <raw key>, noBearer. Filters are nested objects, not flat arrays:filters.result_role.include,filters.result_country_code.include,hide_legal_entities. SendcountOnly: trueto count for free; without it every returned row costs a credit. The full spec is athttps://docs.basile.cc/openapi.yaml, and an AI oriented index athttps://docs.basile.cc/llms.txt. Read the spec before writing a filter, because a query written from intuition will be rejected. - LinkedIn, through a Sales Navigator search URL and the Emelia LinkedIn scraper. Best for a named account list or a role-based search outside France.
- A CSV the user already has. Always supported, no key needed. Map the columns, then treat it exactly like a sourced list.
Enrichment (email, mobile, verification) always goes through Emelia, which bills in credits. See "Credit discipline" below.
Connecting to Emelia
Two ways in, and they do not cover the same ground. Read this before writing any call.
The REST API, the documented path
This is what every sub-skill assumes by default. The user creates an API key at app.emelia.io/settings/api and exports it:
export EMELIA_API_KEY="..." # required for anything that enriches or sends
export BASILE_API_KEY="..." # optional, French B2B data
Base https://api.emelia.io, header Authorization: <key>. Never write a key into a
file. The full surface, which is all there is:
| Method and path | What it does |
|---|---|
POST /tools/find/email then GET /tools/find/email/{jobId} | Find an email from fullname, companyName or companyWebsite, and country, which is required despite what some schemas say |
POST /tools/find/phone then GET /tools/find/phone/{jobId} | Find a mobile from linkedinUrl |
POST /tools/verify/email then GET /tools/verify/email/{jobId} | Verify one address |
GET /lists/list, POST /lists/list | Read the lists with their columns, create one. Returns listId |
POST /lists/list/{listId}/contacts/batch | Load up to 100 contacts per call, ?updateIfExists=true to re-run safely |
GET /lists/list/{listId}/rows | Read the contacts back, paginated, pageSize caps at 100 |
POST /advanced/lists/contacts | Add one contact to a list, for a single row, never for a file |
POST /advanced/campaign/contacts | Add a contact straight into an email campaign, with custom fields |
PATCH /advanced/contacts | Set a custom field on a contact |
POST /advanced/campaigns | Create a campaign. Takes a name and nothing else |
GET /advanced/campaigns | List campaigns |
GET /advanced/campaigns/{campaignId}/activities | Activity feed: sent, opened, clicked, replied, bounced |
POST /emails/reply | Reply to a reply: messageId, providerId, to, subject, content |
POST and DELETE /emails/blacklists/contact | Add or remove an address from the blacklist |
POST /linkedin/campaigns, GET /linkedin/campaigns | LinkedIn campaigns |
POST and DELETE /linkedin/campaign/contacts | Contacts in a LinkedIn campaign |
POST /linkedin/lists/contacts, PATCH /linkedin/contacts | LinkedIn lists and custom fields |
Rate limits, per minute and per key: 30 without a subscription, 100 on Start, 300 on Grow, 1000 on Scale. Batch and pace accordingly.
Building and launching a campaign, end to end
These routes are not in the public documentation yet, and they were verified against the live API on 8 September 2026 with a plain API key. They are what makes the whole pipeline programmable, so use them, and tell the user they are not contractual yet.
| Method and path | What it does |
|---|---|
GET /advanced/campaigns/{id} | The full campaign: status, steps, schedule, recipients, identities |
PATCH /advanced/campaigns/{id}/steps | Set the sequence. The body wraps the sequence in an object, not an array |
PATCH /advanced/campaigns/{id}/settings | Schedule, daily volumes, tracking, stop conditions, wrapped in settings |
PATCH /advanced/campaigns/{id}/recipients | Attach lists (lists) and exclusion lists (excludedLists) |
PATCH /advanced/campaigns/{id}/identities | Choose the sending accounts |
POST /advanced/campaigns/{id}/test-email | Send yourself a test before launching |
POST /advanced/campaigns/{id}/start and /pause | Launch and pause. No body |
PATCH /advanced/campaigns/{id}/planned-start | Schedule the launch for later |
POST /advanced/campaigns/{id}/duplicate and /archive | Duplicate, archive |
DELETE /advanced/campaigns/{id} | Delete a campaign, useful to clean up a test |
GET /advanced/campaigns/{id}/progress, /failed-activities, /export | Follow a running campaign |
A freshly created campaign already carries a default schedule (35 contacts added per
day, 500 emails maximum, Europe/Brussels, Monday to Friday, 08:00 to 17:00) and four
empty steps. Read it back with GET before patching, so you change what you mean to
change and nothing else.
The order that works: create, patch the steps, patch the settings, attach the list, choose the identities, send yourself a test email, then start. Never skip the test.
The MCP server, an optional accelerator
If the user has it configured (https://mcp.emelia.io/mcp, header Authorization),
prefer it for the five things the REST API does not expose: get_campaign_stats,
get_warmup_status and set_warmup, list_email_providers, the list management
tools (create_list, list_lists, get_list, rename_list, delete_list,
get_list_contacts, update_contact, delete_contact) and find_contact, which
searches a contact across every list of the account.
Never assume it is there. Check, and fall back to REST or to asking the user.
Two things verified against the live server on 8 September 2026, because getting them
wrong costs a failed call every time: find_email rejects any request without
country, even though its schema lists only fullname and companyName as
required; and find_contact takes a single query parameter, not email, whatever
its description suggests. Enrichment jobs answer in under five seconds, so poll every
three seconds rather than every thirty. Bulk contact adds cap at 100 rows per call and
return a created / duplicates / updated / failed breakdown you should report
to the user as is. Deleting a list deletes its contacts too, and updates the campaigns
that used it.
What no API can do, and what that means for you
The whole pipeline is programmable, but two things still deserve care.
A campaign is never launched without the user saying so. Not because the API stops you, but because a message to a real person is not reversible. Show the copy, the recipient count and the daily volume, send the test email, then ask.
Contacts live in lists. A list attached to a running campaign feeds it continuously: every contact you add enters the campaign on its own. That is the product's central mechanic, and it is what makes an ongoing campaign easy to feed without touching its configuration again.
Say this plainly to the user the first time, rather than letting them discover it. If neither key nor MCP is configured, offer dry run: every step still produces its file, nothing is sent, nothing is charged.
Why these skills exist next to an API
An endpoint tells you how to call it. It does not tell you to count before you extract, to filter before you enrich, that a catch-all domain cannot be verified, that a 40% open rate with no replies is an offer problem and not a deliverability one, or that adding a thousand contacts to a live campaign on a cold domain will burn it. That judgment is what each sub-skill carries. The transport is a detail.
The rules that make this trustworthy
Credit discipline. Finding an email, a mobile or a verification costs the user money. Before any bulk enrichment: count the rows, state the cost in credits, and get an explicit yes. Never enrich a row you are going to filter out later, so filter first.
Verify before you send. A list that has not been verified will bounce, and bounces cost sender reputation, which is far more expensive than the credits saved. Sending to an unverified list is a refusal, not a warning.
Deliverability is a gate, not a chapter. /outreach campaign calls
outreach-deliverability first. If warmup is off, if SPF, DKIM or DMARC is missing,
or if the daily volume is above what the mailboxes can carry, stop and fix it.
Identify yourself, and offer a way out. Every message says who is writing and for which company, and every step from the second onwards carries a working unsubscribe link. That is not paperwork, it is what separates outreach from spam, and a reader who cannot opt out marks you instead.
Say what you did not do. If the finder found 62% of the list, say 62%, do not round it up and do not quietly drop the rest.
How a full run flows
/outreach full "sell our API monitoring tool to CTOs of French SaaS, 20 to 200 people"
1 outreach-offer -> offer.md what you sell, and why it is not generic
2 outreach-icp -> icp.json you confirm before spending
2 outreach-leads -> leads.csv count first, extract second
3 outreach-filter -> leads.csv dedupe, exclude, segment
4 outreach-enrich -> enrichment.json find, verify, cost stated up front
5 outreach-write -> sequence.md copy, reviewed by you
6 outreach-personalize -> leads.csv icebreakers per contact
7 outreach-sequence -> campaign.json steps, delays, conditions, A/B
8 outreach-deliverability -> gate blocks the launch if unsafe
9 outreach-campaign -> live in Emelia you confirm the launch
10 outreach-replies -> replies triaged labels and drafts, you send
11 outreach-audit -> audit.md what is wrong with the emails
Steps 1, 4, 5 and 9 stop and wait for the user. The rest runs through.
Speed, and why it is a correctness question
A run that takes two hours is not a slow run, it is a wrong one: the user stops watching, cannot correct you, and you finish alone. Three rules, in order of how much time they save.
Never wait on one row before starting the next. The finders are asynchronous. POST /tools/find/email returns a jobId immediately and GET /tools/find/email/{jobId}
answers when the server is done. Submit the whole file, then poll the whole file.
Submitted serially, 250 rows take between one and three hours, because each one waits
out the server's own search. Submitted as a wave, the same 250 come back in about the
time the slowest single row takes, which is a minute or two.
scripts/find-emails.py does exactly that and it is the reference implementation.
The /tools/ routes are exempt from the rate limit of the plan, so nothing is gained by
holding back.
Move on at the number you need, not at the last row. --enough 120 stops the
polling as soon as 120 addresses are in. The jobs still running are not lost: their
jobId is written into the file and GET /tools/find/email/{jobId} collects them
later. Building the campaign with the contacts you already have, and folding in the
stragglers afterwards, is always faster than waiting for a complete file.
Fan out everything that divides. This is a Claude Code plugin, and a step that
loops over items should be several agents, not one loop. Writing 100 personalised
sequences is ten agents of ten contacts each, not one agent of a hundred. Sourcing from
three cities is three agents. Auditing nine mailboxes is nine. Give each agent the same
written brief, a disjoint slice, and its own output file, then read the outputs back,
check them against the brief yourself, and reconcile. The agents/ directory holds the
briefs that already exist: outreach-lead-sourcer (several sources at once),
outreach-enricher (batches), outreach-copywriter (a slice of contacts each),
outreach-deliverability-auditor (one mailbox each), outreach-auditor (per campaign
or per step).
Two things do not parallelise, and forcing them costs more than it saves: anything that writes the same file, and anything that spends credits on rows another agent may already have covered. Split the input first, then fan out.
Say what is happening, while it happens
The user is in a conversation with you, not reading a log afterwards. When you fan work out to agents, or start something that runs for a minute, say so before it starts and report each result as it lands: what you launched, what came back, what it cost, what you are doing next. A silent stretch longer than a couple of minutes is a bug in how you are working, not a property of the task.
The same rule applies inside Emelia, because that is where the user will look. Create the campaign early and let it fill up in the open. Create it as soon as you know the name, with the state in the name in parentheses, and rename it as the run advances:
Croitr pilote 1 (en attente des contacts)
Croitr pilote 1 (100 contacts charges, redaction en cours)
Croitr pilote 1 - agences com Bordeaux
Push the contacts as soon as they exist, before the copy is written. The batch route
takes ?updateIfExists=true, so pushing the same rows again later with their message
columns updates them in place rather than duplicating them. A user who opens Emelia
mid-run should see a campaign filling up, not an empty account.
Keep the plugin up to date
This repository moves. /outreach update runs the check on demand, and you run it
yourself at the start of a run and once a day inside a long one:
bash scripts/check-update.sh
One call to the remote, one line back: up to date, or
behind by N commits, run: git -C <dir> pull.
When it says behind, tell the user in one sentence, with the command, and say what changed if you can see it. Do not pull on their behalf: it is their checkout, and a run in progress should not have its instructions swapped underneath it. Finish the run, then suggest the pull.
Sub-skills
Each command above maps to a skill in skills/outreach-*. Read the matching
SKILL.md before acting: it holds the field names, the API shapes, the benchmarks
and the failure modes for that step. Do not reimplement a step from memory.
Limits worth stating to the user
This skill does not place calls, does not scrape LinkedIn outside the official Emelia integration, does not buy data from grey market brokers, and does not guarantee an email is deliverable when the domain is catch-all. Reply rates depend on your offer far more than on your tooling: when the copy is the problem, say it.
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.
- sales-outreach by zubair-trabzada · 1,318
- apollo-outreach by OpenClaudia · 691
- outreach-sequence-builder by Varnan-Tech · 645
- cold-outreach-sequence by BrianRWagner · 416
- cold-outreach by shawnpang · 324
- earned-media-outreach by shawnpang · 324
- partnership-outreach by shawnpang · 324
- cold-email-outreach by thatrebeccarae · 139
Need help setting it up?
This page tells you what outreach 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.