Find leads, reach them on email, LinkedIn and WhatsApp,
and let AI work every reply, from one workspace you run yourself.
Website · Docs · Self-host · Community · Report a bug
Replies from every channel land in one queue. AI classifies each and drafts the answer. Watch the 51-second film. Every name in it is fictional.
Outbound usually means stitching together a lead database, an enrichment tool, an email sequencer, a LinkedIn tool, a dialer and a CRM, each with its own seats, its own copy of your leads and its own idea of who replied. Most of them are thin layers over APIs you could call yourself.
- One workspace instead of six tools. One record per person and company, so a reply on LinkedIn shows up in the same queue and pipeline as an email reply.
- The AI does the tracking. Every reply, on every channel, is read, classified and given a drafted answer. You approve what goes out.
- You own it. A single Next.js app and one PostgreSQL database on your own server. Your leads, conversations and recordings never leave infrastructure you control.
- Your keys, no seats. Connect your own Google Workspace, Unipile, storage and any AI model on OpenRouter. No per-seat or per-contact fees: you pay your server and your providers.
Three channels, one lead record. Email from your own mailboxes, LinkedIn invites and messages, WhatsApp chats and calls. How it fits together → |
Enrichment, built in. Tables run 15 providers (Apollo, Hunter, Lusha and more), AI columns and HTTP calls, row by row. Tables → |
Email sequences from your own mailboxes. Google Workspace, merge fields, sending windows and daily limits that keep inboxes healthy. Email → |
LinkedIn invites and messages. Connection requests with notes, follow-ups after acceptance, inside conservative daily limits. LinkedIn → |
WhatsApp chats and recorded calls. Message campaigns, and calls dialed from AgentSDR, recorded to your bucket and transcribed. WhatsApp → |
AI reads every reply. Interested, meeting requested, not now, out of office: classified with the reason shown, and a draft ready. AI CRM → |
Approve and send in one click. Edit the draft if you like; it goes out on the channel the lead wrote on. Nothing sends on its own. Drafts → |
A pipeline that keeps itself current. Every lead sits where it stands, and follow-up sequences are scheduled for you. Pipeline → |
See what turns into meetings. Replies, positive replies, meetings and customers, by channel and over time. Analytics → |
Self-host it in minutes. Download one file, set a few secrets, |
Also inside: a lead database with custom fields, CSV and XLSX import with merge fields, Do Not Contact across every channel, organizations with roles, teams and invitations, and per-organization sending rules with safe defaults.
Each organization connects its own accounts in Settings, with a guided setup for every field. All integrations →
| Service | Powers | Setup guide |
|---|---|---|
| Google Workspace | Sending and reading Gmail, through a service account | Google Workspace (with a 2-minute video) |
| Unipile | LinkedIn and WhatsApp accounts, messaging and calling | Unipile |
| Cloudflare R2 | Call recordings and contact photos | Cloudflare R2 |
| OpenRouter | Every AI feature, on your own key and any model | OpenRouter |
| 15 enrichment providers | Tables columns | Enrichment providers |
| Resend | Sign-up, password-reset and invitation email | Resend |
You need a machine with Docker.
mkdir agentsdr && cd agentsdr
curl -O https://raw.githubusercontent.com/Kandid-ai/AgentSDR/main/docker-compose.yml
docker compose up -d # PostgreSQL, the app on :3000 (it creates the schema and runs the scheduled jobs)Open http://localhost:3000: the setup page creates your admin account and organization. Then connect your services in Settings. For a server, replace the # CHANGEME values in docker-compose.yml (or a .env beside it) first; openssl rand -hex 32 makes a secret.
The Docker Compose guide walks through every setting and every service. There are also guides for running from source, deploying on a VPS with Dokploy and going to production.
Run it from source
You need Bun 1.2+ and PostgreSQL 16+.
git clone https://github.com/Kandid-ai/AgentSDR.git && cd AgentSDR
bun install
createdb agentsdr
cp .env.example .env.local # set DATABASE_URL, BETTER_AUTH_SECRET, INTEGRATION_CREDENTIALS_KEY
bun run db:setup # create the schema in the empty database
bun run db:seed:demo # optional: a fictional demo organization
bun run dev # http://localhost:3000With the demo seed, sign in as demo@example.com / demo-password-123.
Environment variables
Environment variables cover only what the app needs before it can read its database. Everything else is per organization, in the app.
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
yes | PostgreSQL 16+ connection string |
BETTER_AUTH_SECRET |
yes | Signs sessions |
BETTER_AUTH_URL |
yes | Public URL, read at runtime |
INTEGRATION_CREDENTIALS_KEY |
yes | Encrypts integration credentials (AES-256-GCM) |
UNSUBSCRIBE_SECRET |
for email | Signs unsubscribe links |
CRON_SECRET, OUTREACH_TICK_SECRET |
no | Only to call the job endpoints from an external cron |
RESEND_API_KEY, AUTH_EMAIL_FROM |
in production | Auth email through Resend |
AUTH_SIGNUP |
no | invite-only (default) or open |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
no | "Continue with Google" |
DB_POOL_MAX |
no | Connection pool size (default 8) |
Every variable, with how to generate it and where it is read: Configuration reference.
flowchart LR
A["Leads<br/>import or build a list"] --> B["Tables<br/>enrich with 15 providers and AI"]
B --> C{"Reach out"}
C --> D["Email<br/>your mailboxes"]
C --> E["LinkedIn<br/>invites and messages"]
C --> F["WhatsApp<br/>messages and calls"]
D --> G["Every reply<br/>one queue"]
E --> G
F --> G
G --> H["AI CRM<br/>classify and draft"]
H --> I["You approve<br/>one click"]
H --> J["Pipeline and analytics"]
One Next.js app and one PostgreSQL database: pages, API routes and background workers run in a single process, with no queue server or Redis. Every request, job and webhook runs inside one organization's scope, and code that forgets fails closed. Integration credentials are encrypted per organization and verified with a live call before they are saved. More in How it works and the architecture guide.
Architecture diagram
flowchart TB
subgraph You["Your infrastructure"]
App["AgentSDR (Next.js)<br/>pages, API, in-process workers and scheduled jobs"]
DB[("PostgreSQL 16+")]
Ext["Call Recorder<br/>Chrome extension"]
end
App <--> DB
Ext -- "recordings" --> App
App <--> Unipile["Unipile<br/>LinkedIn, WhatsApp"]
App <--> Gmail["Google Workspace<br/>Gmail"]
App <--> R2["Cloudflare R2<br/>recordings"]
App <--> OR["OpenRouter<br/>AI models"]
App <--> Enr["Enrichment providers"]
Unipile -- "webhooks" --> App
Gmail -- "Pub/Sub push" --> App
Project structure and development
src/app/ Pages (App Router) and API routes
src/lib/<domain>/ Business logic, schema and queries per domain
src/components/ React components, one folder per domain
src/jobs, src/functions LinkedIn sending engine (run by the in-app scheduled jobs)
src/services/ Unipile API clients
db/ schema.sql (generated snapshot) and seed.sql
scripts/ Migration history, audits, database tooling, e2e checks
extensions/whatsapp-recorder/ Chrome extension that dials and records WhatsApp calls
docker/, docker-compose.yml Self-hosting
docs/ Documentation (published at docs.agentsdr.ai)
| Command | What it does |
|---|---|
bun run dev |
Start the app at http://localhost:3000 |
bun run test |
600+ unit tests |
bun run typecheck / bun run lint |
Must pass before every commit |
bun run db:setup / db:seed:demo |
Create the schema / a fictional demo organization |
bun run db:check |
Audit that every table is organization-scoped |
CI runs typecheck, lint, tests, a secret scan, the production build, the extension build and a fresh install on PostgreSQL 16, 17 and 18. See the development guide.
Built with Next.js 16 and React 19, TypeScript, Drizzle ORM, Better Auth, Tailwind CSS with AlignUI, and Bun.
- Your data stays with you. Leads, conversations and analytics live in your PostgreSQL; call recordings in your own R2 bucket, reached only through short-lived signed links.
- Encrypted credentials. Every saved API key and token is encrypted with AES-256-GCM.
- Tenant isolation, tested. An end-to-end suite signs in as a second organization and proves it can see, change and count nothing of the first.
- Humans approve outbound replies. AI drafts wait for a person, and outbound work can be paused instantly.
- Report vulnerabilities privately through GitHub security advisories. See SECURITY.md.
AgentSDR contacts people on your behalf, so how it is used is your responsibility. LinkedIn's terms do not allow automation and accounts can be restricted; WhatsApp bans numbers that send unsolicited bulk messages; cold email is regulated by laws such as CAN-SPAM, GDPR and CASL; and many places require consent before a call is recorded. Sending limits, unsubscribe links and Do Not Contact reduce the risk but do not make any use compliant. Read Responsible use before you start.
Is AgentSDR free?
Yes. It is open source and you host it yourself, so there are no seats, tiers or per-contact fees. You pay for your server, the accounts you connect (Google Workspace, Unipile) and your own AI usage. It is MIT-licensed, so you can also use, change and build on it commercially.
Which email providers can it send from?
Google Workspace mailboxes, connected once through a service account with domain-wide delegation. Each mailbox has its own daily limit (30 by default), sending hours and signature. Microsoft 365 is not supported yet.
Will LinkedIn automation get my account restricted?
No tool can promise that, but AgentSDR stays well inside LinkedIn's limits: 30 invitations a day on premium accounts and 5 on free, a random 30 to 60 second gap between invitations, optional working hours per account, and an account that hits LinkedIn's own limit pauses for the day.
How does WhatsApp calling work?
You link your number through Unipile and install the AgentSDR Call Recorder Chrome extension. When you call a lead from AgentSDR, the extension dials inside WhatsApp Web and records both sides; the recording goes to your storage bucket and your chosen model transcribes it. It relies on WhatsApp Web's English interface.
Which AI models can I use?
Any model on OpenRouter, with your own key. Each call is pinned to the provider you picked, with fallbacks turned off, so your data only goes where you chose.
Does the AI send replies on its own?
No. Drafts, including follow-up sequence steps, wait in Action required until someone sends them, as written or edited. The AI can move a lead forward in the pipeline when it is confident; anything else is held for a person.
Can my whole team use it?
Yes. Invite teammates from Settings → Members with the owner, admin or member role. One deployment can also hold several organizations, each fully separate.
| Where | Best for |
|---|---|
| Docs | Setup guides, every feature, troubleshooting |
| GitHub Discussions | Questions, ideas, and showing what you built |
| GitHub Issues | Bugs and feature requests (in the app: Help → Report a bug) |
| Security advisories | Vulnerabilities, reported privately |
Contributions are welcome: bug reports, fixes, enrichment providers, docs. Start with CONTRIBUTING.md for the development setup, the checks a pull request must pass and the commit conventions; issues labelled good first issue are a good way in. Also see the Code of Conduct, how the project is run and what changed.
AgentSDR is open source under the MIT License:
- You may use, copy, modify, self-host, distribute and sell AgentSDR, including in commercial products and as a hosted service.
- You don't have to publish your changes, though contributions back are always welcome.
- Keep the copyright and license notice in copies you distribute.
The LICENSE file is the binding text.
Acknowledgements
- The sign-in page photograph is by Marek Piwnicki on Unsplash.
- Built on Next.js, Drizzle, Better Auth, AlignUI and Remix Icon.









