Agentic Email Automation Tool: Describe your product. Define your target market. The AI finds the leads for you.
Agentic Email Automation Tool: Describe your product. Define your target market. The AI finds the leads for you.
Describe your product. Define your target market. The AI finds the people who fit, tells you why each one does, and emails them.
Self-hosted CLI. One install, one onboarding, one command.
OpenOutreach is a self-hosted, open-source lead finder that qualifies for you — and then writes the email. You describe your product and your target market; it discovers matching people from a licensed data provider, judges each one against the ICP it learned from your description, hands you the ones that fit with the reason each was chosen written out, and opens the conversation from your own mailbox.
Two things make that different from what you may have used before:
It has zero platform-ToS surface: browserless, no social-network account, no scraping. There is no account to get banned, because there is no account.
uv tool install openoutreach
openoutreach
That is the whole thing. A bare openoutreach onboards you if it has to, finds leads that fit,
buys a verified work address for each, and emails them from your mailbox — narrating what it did as
it goes. One install, one wizard, one command.
Prefer to say it out loud, or to set the size of the first run?
openoutreach run 5 # find five leads carrying an address, then send — at most 5 credits
The verbs:
…
Everything lives in ~/.openoutreach, so stopping and starting loses nothing: the number you ask
for is more than you already have, so running it again continues where it left off. No browser,
no daemon manager, no container.
This repo ships a Claude Code plugin, so you can pull leads without leaving your agent session:
/plugin marketplace add eracle/OpenOutreach
/plugin install openoutreach@openoutreach
The skill (skills/find-leads/SKILL.md) teaches Claude when to run find, which flags cost credits
and which cannot, how to read the CSV on stdout, and what each error: <type> means. It never buys
an address you did not ask for, never sends without being asked, and never accepts the legal notice
for you. Prefer skills to plugins? Copy skills/find-leads/ into ~/.claude/skills/ instead.
Not on Claude Code? The skill is a markdown file describing the CLI's own contract — nothing
Claude-specific is required to use openoutreach itself. Codex, Cursor, or any other agent can call
the same openoutreach find / send / run / status commands directly; point your agent's
instructions file at skills/find-leads/SKILL.md and it reads the same rules.
OpenOutreach is an orchestrator. The finding and the sending are two standalone programs, and this package installs both and hosts them in one process, one database and one onboarding:
| Package | What it is | Standalone |
|---|---|---|
| OpenOutFind | discovery, qualification, enrichment, the CRM | uvx --from openoutfind outfind find 10 |
| OpenOutSend | the outreach agent, the mailbox, the send guards | uvx --from openoutsend outsend send |
| OpenOutreach (this) | one install, one wizard, one command over both | — |
The wizard is here because the children do not have one. Both are agent-first: they read their
configuration from OPENOUTFIND_* / OUTSEND_* on every run and remember none of it, which is
right for a program a script or an agent drives and wrong for a person. So this is where the
questions are asked, where the answers are kept, and where they are handed to each child in its own
variables.
Neither child is diminished by the bundle. Each keeps its own console script, its own settings module and its own test suite, and the contract between them is a public one:
outfind find 50 --json | outsend # anybody's producer, anybody's receiver
outsend send # a separate invocation, on the mailbox's clock
openoutreach run is that same pipe, in one process — the JSON Lines still cross the boundary, they
just cross it in a buffer. There is no privileged in-memory hand-off, because a second, untested path
between the same two programs would make the public one a lie.
Which shape is for you: if you are an agent, a script, or a power user with your own sender, take the two CLIs and the pipe. If you want to see whether this works, take the one command.
The finder's deliverable is a file, and it is shaped for the tools you already send with:
openoutreach find 10 emails > leads.csv
It runs until it has ten more leads carrying an address, prints every lead you have as CSV, and exits — so the file you just wrote is always the current truth. Exit 0 means it got what you asked for; anything short still prints its rows and says why it stopped.
email, first_name, last_name, company, title, website, linkedin_url, reason, lead_id, qualified_at
Those column names are the importers', not ours. Instantly and Smartlead both require
email/first_name/last_name and recognise company/title/website/linkedin_url as standard
fields, so an exported file imports without column mapping. Anything else — including reason —
arrives as a custom variable you can merge into a template.
reason is the point. Everybody exports rows; almost nobody exports why this lead.If you send with your own tool: turn on its import dedupe. It is opt-in on Smartlead and undocumented on Instantly, so a lead you export twice can otherwise be contacted twice.
| # | What | Example |
|---|---|---|
| 1 | An LLM API key | OpenAI, Anthropic, or any OpenAI-compatible endpoint |
| 2 | An email-finder API key (BetterContact) | Free account: 40 credits, no card. Powers both discovery (Lead Finder, billed nothing) and enrichment (one credit per verified work email) |
| 3 | A product description + target market | "We sell cloud cost optimization for DevOps teams at mid-market SaaS companies" |
| 4 | A mailbox to send from | Its address and an app password — not your login password. Google Workspace works out of the box; any other provider names its SMTP/IMAP host and port |
Onboarding asks for all four in one pass and asks only once — the answers are kept in
~/.openoutreach, and every later run exports them into the variables the children read. A question
whose variable is already exported is not asked at all, so an operator or a unit file that sets
OPENOUTFIND_* / OUTSEND_* never has to repeat it into a prompt; that is what makes a headless
install possible. With no TTY and something still unanswered, setup stops naming the variables
that would have answered it rather than blocking on a question nobody is there to hear.
The two long fields are read from files, not flags — a markdown paragraph shell-quoted onto a command line corrupts quietly:
openoutreach init --product-docs product.md --target target.md
The LLM key is verified at the prompt, and the mailbox by a real SMTP login before setup finishes: a wrong key is an answer you can retype, not a traceback halfway through a run.
The BetterContact link above is an affiliate link — signing up through it supports OpenOutreach, at no markup to you.
Why choose OpenOutreach?
uv tool install openoutreach && openoutreachEvery comparable tool that qualifies leads for you is paid SaaS. This one is GPLv3, runs on your machine, and you bring your own provider keys.
Affiliate links, and that is now the whole of it. The one paid third-party service the tool relies on — the lead-data provider — is surfaced during onboarding through an affiliate link. Sign up through it and the project may earn a commission, at no markup to you. Sign up any other way if you prefer. See the Legal Notice (§4).
Discover → qualify → gate → resolve → write → send.
Steps 1–5 are OpenOutFind's and step 6 is OpenOutSend's; each repo documents its own internals. Searching the licensed source is free, so the system can afford to look at a lot and spend paid lookups only on the best fits. (The learning loop is an active experiment — it is not yet shown to beat picking at random, and no claim is made that it does.)
This repo is the orchestrator and holds no pipeline of its own — that is the point of it:
…
git clone https://github.com/eracle/OpenOutreach.git
cd OpenOutreach
make setup # install -e ".[dev]" + migrate both children's apps
make run # onboard, find, send
make test
Working on the pipeline itself? It is not here — clone
OpenOutFind or
OpenOutSend, and point this project at your checkout with
uv pip install -e ../OpenOutFind.
Running it on a server instead? A Docker image is published to GitHub Container Registry for exactly that — see the Docker Guide.
Join for support and discussions: Telegram Channel
No open issues yet, or sync has not completed.