
Describe your product. Define your target market. The AI finds the leads for you.

OpenOutreach is a self-hosted, open-source outreach tool for B2B lead generation that uses LinkedIn for discovery and email for outreach. Unlike other tools, you don't need a list of profiles to contact — you describe your product and your target market, and the system autonomously discovers, qualifies, and contacts the right people.
How it works:
The system gets smarter with every decision. It starts by exploring broadly, then progressively focuses on the highest-value profiles as it learns your ideal customer profile from its own classification history.
Why choose OpenOutreach?
Perfect for founders, sales teams, and agencies who want powerful automation without account bans or subscription lock-in.
| # | What | Example |
|---|---|---|
| 1 | A LinkedIn account | Your email + password |
| 2 | An LLM API key | OpenAI, Anthropic, or any OpenAI-compatible endpoint |
| 3 | A product description + target market | "We sell cloud cost optimization for DevOps teams at mid-market SaaS companies" |
That's it. No spreadsheets, no lead databases, no scraping setup.
To turn on the email channel (optional — LinkedIn-only outreach works without it), the onboarding nudge also asks for:
| What | Why |
|---|---|
| An email-finder API key (BetterContact) | Resolves a work email for each qualified lead |
| A sending mailbox (cold-email infra — IceMail) | Sends from your own SMTP box, paced under a per-box daily cap |
If neither is configured, every qualified lead simply routes to the LinkedIn connection channel.
Pre-built images are published to GitHub Container Registry on every push to master.
docker run --pull always -it -e ENABLE_VNC=true -p 5900:5900 -p 6080:6080 -v ~/.openoutreach/data:/app/data ghcr.io/eracle/openoutreach:latest
# Open http://localhost:6080/vnc.html in your browser to watch the automation live
The interactive onboarding walks you through the three inputs above on first run. All data persists in ~/.openoutreach/data on your host across restarts.
Once the container is running, open http://localhost:6080/vnc.html in your browser to watch the browser live (noVNC). Alternatively, connect a native VNC client to localhost:5900.
-e ENABLE_VNC=trueis required for the viewer. The VNC stack (Xvfb + x11vnc + noVNC) is installed in the image but only started whenENABLE_VNC=true. Without it, nothing listens on 5900/6080, so the published ports accept then instantly drop the connection (End of stream).
For Docker Compose, build-from-source, and more options see the Docker Guide.
For contributors or if you prefer running directly on your machine.
git clone https://github.com/eracle/OpenOutreach.git
cd OpenOutreach
# Install deps, Playwright browsers, run migrations, and bootstrap CRM
make setup
make run
The interactive onboarding will prompt for LinkedIn credentials, LLM API key, and campaign details on first run. Fully resumable — stop/restart anytime without losing progress.
OpenOutreach includes a full CRM web interface powered by DjangoCRM:
# Create an admin account (first time only)
python manage.py createsuperuser
# Start the web server
make admin
Then open: - Django Admin: http://localhost:8000/admin/
| Feature | Description |
|---|---|
| 🧠 Autonomous Lead Discovery | No contact lists needed — LLM generates search queries from your product description and campaign objective. |
| 🎯 Bayesian Active Learning | Gaussian Process model on profile embeddings learns your ideal customer via explore/exploit, selecting the most informative candidates for LLM qualification. |
| 🤖 Stealth Browser Automation | Playwright + stealth plugins mimic real user behavior for undetectable interactions. |
| 🛡️ Voyager API Scraping | Uses LinkedIn's internal API for accurate, structured profile data (no fragile HTML parsing). |
| 📧 Email Outreach Channel | Resolves a work email per qualified lead (BetterContact) and sends an AI-written opener from your own mailbox over SMTP — the high-volume channel. Leads with no email fall back to LinkedIn. |
| 🔄 Stateful Pipeline | Tracks profile states (QUALIFIED → email fork READY_TO_EMAIL → EMAILED, or LinkedIn READY_TO_CONNECT → PENDING → CONNECTED → COMPLETED) in a local DB — fully resumable. |
| ⏱️ Smart Rate Limiting | Configurable daily/weekly limits per action type — respects LinkedIn's own caps, and paces email under a per-mailbox daily cap. |
| 💾 Built-in CRM | Full data ownership via DjangoCRM with Django Admin UI — browse Leads, Contacts, Companies, and Deals. |
| 🐳 One-Command Deployment | Dockerized setup with interactive onboarding and a live browser view in your browser (noVNC at http://localhost:6080/vnc.html). |
| ✍️ AI-Powered Messaging | Agentic multi-turn follow-up conversations — the AI agent reads history, sends messages, and schedules future follow-ups. |
OpenOutreach's LinkedIn layer is also published as a standalone, Django-free package —
linkedin-agent-cli — so you can let your own
LLM agent drive LinkedIn directly, no OpenOutreach install required. Every verb prints a human
summary or the full result dict with --json, and errors go to stderr with stable types — a
clean tool-use contract any agent (or any language) can call:
pip install linkedin-agent-cli
python -m playwright install chromium
linkedin-cli session open --session work # launch + bind a browser (this process owns it)
linkedin-cli login --session work # authenticate in that session
linkedin-cli search "head of growth" --network first --json # → handles your LLM can parse
linkedin-cli profile alice-smith --json # → full profile dict
linkedin-cli message alice-smith --session work --text "Hi Alice"
Point your agent at the --json output and the per-verb --help; see the
linkedin-cli README for the full verb surface
and output contract.
The daemon runs a continuous task queue backed by a persistent Task model. Four task types self-schedule follow-on work:
| Task Type | What it does |
|---|---|
| Connect | Ranks qualified profiles by GP model probability, sends connection requests (daily + weekly limits). Triggers qualification and search via composable generators when the pool is empty. |
| Check Pending | Checks if a pending request was accepted (exponential backoff per profile) |
| Follow Up | Runs an AI agent that manages multi-turn conversations with connected profiles |
| Sends one AI-written opener to a lead with a resolved work email, from your mailbox pool (single-shot, paced by per-mailbox daily cap) |
Channel routing at qualification: when a profile is qualified, OpenOutreach tries to resolve a work email (BetterContact finder, gated by a configured key). A hit forks the lead onto the email channel (READY_TO_EMAIL → one opener → EMAILED, where it rests until you set an outcome — Layer 1 sends one opener and does not yet read replies). A miss (or no finder key) leaves the lead on the LinkedIn path, where the GP confidence gate promotes it to READY_TO_CONNECT. The fork is encoded in the Deal state, so each lead is contacted on exactly one channel and emailed at most once.
The qualification loop in detail:
Profiles discovered during navigation are automatically scraped and embedded (384-dim FastEmbed vectors). The connect task's backfill chain decides which profile to evaluate next using a balance-driven strategy:
All qualification decisions go through the LLM. The GP model selects which candidate to evaluate next and gates promotion from QUALIFIED to READY_TO_CONNECT (confidence threshold). Every LLM decision feeds back into the model, making candidate selection progressively smarter.
Cold start: With fewer than 2 labelled profiles, the model can't fit — candidates are selected in order and qualified via LLM. As labels accumulate, the GP becomes better at selecting high-value candidates.
Configure rate limits and behavior via Django Admin (LinkedInProfile + Campaign models).
├── docs/
│ ├── architecture.md # System architecture
│ ├── configuration.md # Configuration reference
│ ├── docker.md # Docker setup guide
│ ├── templating.md # Follow-up messaging guide
│ └── testing.md # Testing strategy
├── openoutreach/ # single source package; Django apps nested inside
│ ├── settings.py # Django/CRM settings (SQLite at data/db.sqlite3)
│ ├── core/ # engine app: daemon, task queue + scheduler,
│ │ # Campaign/SiteConfig/Task, LLM factory, onboarding,
│ │ # follow-up agent, db helpers, management commands
│ ├── linkedin/ # LinkedIn channel app: browser, discovery pipeline,
│ │ # ML qualifier, task handlers, channel models
│ ├── emails/ # email channel app (Layer 1 of the email-first pivot)
│ ├── crm/ # Lead + Deal models
│ └── chat/ # ChatMessage model
├── manage.py # Django management (no args defaults to rundaemon)
├── local.yml # Docker Compose
└── Makefile # Shortcuts (setup, run, admin, test)
Join for support and discussions: Telegram Channel
Got a specific use case, feature request, or questions about setup?
Book a free 15-minute call — I'd love to hear your needs and improve the tool based on real feedback.
[![Book a 15-min call](ht
$ claude mcp add OpenOutreach \
-- python -m otcore.mcp_server <graph>