Open-source lead management & outreach platform. Discover businesses, organize them into projects, and run email campaigns — all from a single self-hostable app.
- 🔍 Lead Discovery — Search for businesses via Google Places API and enrich them with contact details
- 📁 Projects — Organize leads into projects with custom fields and attributes
- 📧 Email Campaigns — Create and send HTML email campaigns to lead groups, track responses per lead
- 🤖 AI-Powered Search — Generate discovery queries using LLMs via OpenRouter
- 👥 Multi-Org — Role-based access with organization management and user invitations
- 🔒 Self-Hostable — Deploy with Docker in minutes, keep your data under your control
| Layer | Technology |
|---|---|
| Framework | SvelteKit |
| Language | TypeScript |
| Database | PostgreSQL + Drizzle ORM |
| Auth | Better Auth |
| Styling | Tailwind CSS v4 |
| Components | Bits UI |
| Monorepo | Turborepo + Bun |
| Deployment | Docker (Node adapter) |
# Clone the repository
git clone https://github.com/simpros/leader.git
cd leader
# Install dependencies
bun install
# Start PostgreSQL and Mailpit
docker compose up -d
# Set up environment variables
cp .env.example .env
# Edit .env with your API keys
# Generate auth schema and run migrations
bun run auth:generate
bun run db:generate
bun run db:migrate
# Start the development server
bun run devThe app is available at http://localhost:5173.
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
BETTER_AUTH_SECRET |
Secret key for session encryption |
BETTER_AUTH_BASE_URL |
Public URL of the app |
| Variable | Required | Description |
|---|---|---|
GOOGLE_PLACES_API_KEY |
Yes | Google Places API key for lead discovery |
BRAVE_API_KEY |
No | Brave Search API key for enrichment |
OPENROUTER_API_KEY |
No | OpenRouter API key for AI-powered search |
OPENROUTER_MODEL |
No | OpenRouter model name (default: openai/gpt-4o-mini) |
Google Places API Key (required)
- Go to the Google Cloud Console.
- Create a new project (or select an existing one).
- Navigate to APIs & Services → Library and enable the Places API (New).
- Go to APIs & Services → Credentials and click Create Credentials → API key.
- (Recommended) Click Edit API key and restrict it to the Places API (New) under "API restrictions".
- Copy the key and set it as
GOOGLE_PLACES_API_KEYin your.envfile.
Pricing: Google offers a $200/month free tier for Maps Platform APIs. See the Google Maps pricing page for details.
Brave Search API Key (optional — used for lead enrichment)
- Sign up at Brave Search API.
- Choose a plan — the Free tier includes 2,000 queries/month.
- After signing in, go to your API dashboard and copy your API key.
- Set it as
BRAVE_API_KEYin your.envfile.
OpenRouter API Key (optional — used for AI-powered search)
- Create an account at OpenRouter.
- Go to Keys in your dashboard and click Create Key.
- Copy the key and set it as
OPENROUTER_API_KEYin your.envfile. - Optionally set
OPENROUTER_MODELto choose a different model (default:openai/gpt-4o-mini). Browse available models at openrouter.ai/models.
Pricing: Each model has its own per-token price. Many models offer a free tier. See the OpenRouter models page for current pricing.
| Variable | Required | Description |
|---|---|---|
SMTP_HOST |
No | SMTP server host |
SMTP_PORT |
No | SMTP server port (default: 1025) |
SMTP_USER |
No | SMTP username |
SMTP_PASS |
No | SMTP password |
EMAIL_FROM |
No | Sender email address (default: noreply@example.com) |
The official Docker image ships with anonymous telemetry that helps the maintainers identify bugs and prioritize improvements. No personal data is collected — only structured request logs (method, path, status code, duration) are sent to the maintainers' log aggregator.
You can opt out at any time by setting a single environment variable:
| Variable | Default | Description |
|---|---|---|
LEADER_TELEMETRY |
(on) | Set to false to disable telemetry completely |
When telemetry is off, logs are still written to the console — only the remote reporting is disabled.
On first startup the app automatically creates an initial admin user and organization. These env vars customize that behavior:
| Variable | Default | Description |
|---|---|---|
BOOTSTRAP_USER_NAME |
Admin |
Display name of the first user |
BOOTSTRAP_USER_EMAIL |
admin@leader.local |
Email / login of the first user |
BOOTSTRAP_ORGANIZATION_NAME |
Leader |
Name of the default organization |
BOOTSTRAP_ORGANIZATION_SLUG |
leader |
URL slug of the default organization |
A random password is generated and printed to the console on first run. After the initial user exists, subsequent restarts skip creation.
See .env.example for defaults.
Pull the latest image from GitHub Container Registry and run alongside PostgreSQL:
# Download the production compose file
curl -O https://raw.githubusercontent.com/simpros/leader/main/docker-compose.prod.yml
# Create your .env file (set a strong password for both DB vars)
PG_PASS=$(openssl rand -hex 16)
cat > .env <<EOF
POSTGRES_PASSWORD=$PG_PASS
DATABASE_URL=postgresql://postgres:$PG_PASS@db:5432/leader
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
BETTER_AUTH_BASE_URL=https://your-domain.com
GOOGLE_PLACES_API_KEY=your-key-here
EOF
# Start everything
docker compose -f docker-compose.prod.yml up -dThe app is available on port 3000.
leader/
├── apps/
│ └── web/ # SvelteKit application
│ └── src/
│ ├── routes/ # Pages and API endpoints
│ └── lib/ # Shared utilities
├── packages/
│ ├── auth/ # Authentication (Better Auth)
│ ├── db/ # Database schema and migrations (Drizzle)
│ ├── ui/ # Shared Svelte component library
│ ├── eslint-config/ # Shared ESLint configuration
│ ├── tailwind-config/ # Shared Tailwind configuration
│ └── typescript-config/ # Shared TypeScript configuration
├── Dockerfile # Production Docker image
├── docker-compose.yml # Local development services
└── docker-compose.prod.yml # Self-hosting compose file
# Run the development server
bun run dev
# Lint
bun run lint
# Type-check
cd apps/web && bun run check
# Build for production
bun run build
# Format code
bun run format# Run all unit & component tests
bun run test
# Run integration tests (requires Docker)
bun --cwd packages/db run test:integration
# Run E2E tests (requires Docker)
bun --cwd apps/web run test:e2eSee docs/testing.md for the full testing guide — architecture, debugging, CI/CD details, and how to write new tests.
bun run db:generate # Generate migration from schema changes
bun run db:migrate # Apply pending migrations
bun run db:push # Push schema directly (dev only)
bun run db:seed # Seed the database
bun run db:studio # Open Drizzle StudioDocker images are published automatically when you push a git tag:
# Tag a release (uses apps/web/package.json version)
git tag web@0.1.0
git push origin web@0.1.0This builds and pushes to ghcr.io/simpros/leader:0.1.0 and ghcr.io/simpros/leader:latest.
Contributions are welcome! Please open an issue or pull request.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes
- Push to your fork and open a pull request
The official Docker image embeds Dynatrace credentials at build time via Docker BuildKit secrets so end users don't need to configure them. These are stored as GitHub Actions secrets and passed during CI:
| Build Secret | GitHub Secret | Description |
|---|---|---|
DYNATRACE_LOG_INGEST_URL |
DYNATRACE_LOG_INGEST_URL |
Dynatrace log ingest endpoint (/api/v2/logs/ingest) |
DYNATRACE_API_TOKEN |
DYNATRACE_API_TOKEN |
Dynatrace API token with logs.ingest scope |
To build locally with telemetry:
export DYNATRACE_LOG_INGEST_URL=https://xyz.live.dynatrace.com/api/v2/logs/ingest
export DYNATRACE_API_TOKEN=dt0c01.XXXX
docker build \
--secret id=DYNATRACE_LOG_INGEST_URL \
--secret id=DYNATRACE_API_TOKEN \
-t leader apps/webIf neither secret is provided, the image runs with console-only logging (telemetry becomes a no-op regardless of LEADER_TELEMETRY).
This project is licensed under the GNU Affero General Public License v3.0.