A lightweight, multi-tenant CRM built for freelancers, small teams, and growing businesses. Manage contacts, visualize your sales pipeline, and keep your team aligned — without the complexity or price tag of enterprise tools.
Built as an independent SaaS product. Spanish and English supported out of the box.
| Layer | Technology |
|---|---|
| Framework | ASP.NET Core 10 — Razor Pages |
| Language | C# 13 |
| Database | PostgreSQL 16 (Docker locally, Neon in production) |
| ORM | Entity Framework Core 10 + Npgsql |
| Auth | ASP.NET Core Identity |
| CSS | Tailwind CSS 3.4 |
| Resend | |
| Payments | Lemon Squeezy (Merchant of Record) |
| Hosting | Fly.io |
| Local Dev | Docker Compose |
| i18n | ASP.NET Core Localization (Spanish + English) |
- Register creates an organization — not just a personal account
- Admin can invite team members via email
- All members share contacts, deals, and activities within the org
- Two roles: Admin (full control) and Member (create/edit, no settings access)
- Full CRUD with search, filters (by type, tag, assignee), and pagination
- Contact types: Lead, Client, Inactive
- Free-form tags
- Phone number with country code selector (global coverage)
- Detail view: linked deals, activities, and audit timeline
- Visual Kanban board — columns are pipeline stages, cards are deals
- Drag & drop to move deals between stages
- Deal detail: value (USD), expected close date, linked contact, activity log, stage history
- Mark deals as Won or Lost
- Pro: configurable stages (create, rename, reorder, delete)
- Free: predefined stages (Contact → Proposal → Negotiating → Won / Lost)
- Types: Call, Email, Meeting, Task, Note
- Attach to a contact, a deal, or both
- Assign to a team member with a due date
- Views: Today, Upcoming (7 days), Overdue, All
- Pipeline value and open deal count
- Deals won/lost this month
- Pending activities for today
- Recently added contacts
- Pro: per-member breakdown, conversion rate, metrics by stage
- Invite by email (token expires in 7 days)
- Change member roles
- Remove members
- Free plan: 2 users, 100 contacts, 10 active deals
- Pro plan: $29/month or $290/year — unlimited everything
- Checkout via Lemon Squeezy (hosted checkout, no card data handled by the app)
- Subscription managed via Lemon Squeezy Customer Portal
- Webhook-driven plan updates (HMAC-SHA256 verified, idempotent)
- Transactional emails: invitations, activity assignments, daily overdue digest
- Full i18n: language preference per user, not per org (Spanish default or browser language)
- SEO-ready public landing page with pricing table
/healthendpoint for uptime monitoring
Pipelo/
├── src/
│ └── Pipelo.Web/
│ ├── Data/ # DbContext with multi-tenant query filters
│ ├── Models/ # Domain entities (Organization, Contact, Deal, Stage, Activity...)
│ ├── Pages/ # Razor Pages — one folder per feature area
│ ├── Services/ # Email, payments, plan enforcement
│ ├── Helpers/ # Country phone codes, utilities
│ ├── Resources/ # es.resx / en.resx translation files
│ └── wwwroot/ # Static assets
├── tests/
│ └── Pipelo.Tests/
├── docker-compose.yml
└── DEPLOY.md
Every entity holds an OrganizationId foreign key. ApplicationDbContext applies a global query filter using the org_id claim injected at login — no query ever touches another organization's data without an explicit override.
User clicks "Upgrade" → app calls Lemon Squeezy API → redirects to hosted checkout
↓
Lemon Squeezy fires webhook → /webhooks/lemonsqueezy
↓
HMAC-SHA256 verified → event deduplicated → org plan updated
Requirements: .NET 10 SDK, Docker Desktop, Node.js 18+
git clone https://github.com/DMGarcia17/pipelo.git
cd pipelo
docker-compose up -dThis starts PostgreSQL 16 on port 5433.
Create src/Pipelo.Web/appsettings.Development.local.json (excluded from git):
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5433;Database=pipelo_db;Username=pipelo;Password=pipelo"
},
"Resend": {
"ApiKey": ""
},
"LemonSqueezy": {
"ApiKey": "",
"StoreId": "",
"WebhookSecret": "",
"Variants": {
"ProMonthly": "",
"ProAnnual": ""
}
}
}Leaving Resend__ApiKey empty is fine locally — emails are logged to the console instead.
cd src/Pipelo.Web
dotnet ef database update
dotnet run -- seed # optional: populate demo org, users, contacts, deals# Terminal 1 — app
dotnet run
# Terminal 2 — Tailwind watcher
npm install
npm run css:watchApp runs at https://localhost:5001. Demo login (after seeding): admin@demo.com / Demo1234!
| Variable | Description | Required |
|---|---|---|
ConnectionStrings__DefaultConnection |
PostgreSQL connection string | Yes |
Resend__ApiKey |
Resend API key | Production |
LemonSqueezy__ApiKey |
Lemon Squeezy API key | Production |
LemonSqueezy__StoreId |
Lemon Squeezy store ID | Production |
LemonSqueezy__WebhookSecret |
Webhook signing secret | Production |
LemonSqueezy__Variants__ProMonthly |
Variant ID for Pro Monthly | Production |
LemonSqueezy__Variants__ProAnnual |
Variant ID for Pro Annual | Production |
INTERNAL_API_TOKEN |
Bearer token for /internal/* endpoints (overdue digest cron) |
Production |
PLAN_FREE_MAX_CONTACTS |
Override Free plan contact limit (default: 100) | No |
PLAN_FREE_MAX_DEALS |
Override Free plan active deal limit (default: 10) | No |
PLAN_FREE_MAX_USERS |
Override Free plan user limit (default: 2) | No |
In production (Fly.io), set secrets with:
flyctl secrets set LemonSqueezy__ApiKey=your_keySee DEPLOY.md for the full production setup guide (Fly.io + Neon + Cloudflare).
MVP (Sprints 0–10) — complete
- Multi-tenant workspace with invitations
- Contacts CRUD
- Kanban pipeline with drag & drop
- Activities and tasks
- Dashboard and metrics
- Freemium limits
- Lemon Squeezy billing integration
- Email notifications (Resend)
- Public landing page
- i18n (Spanish + English)
V1 — in progress
- Lemon Squeezy store approval
- Production deploy on Fly.io + Neon
- End-to-end payment flow tested
V2 — planned
- CSV import / export for contacts
- Global search (contacts + deals + activities)
- Saved filter views
- REST API for external integrations
- QuoteLab integration (auto-create quote when a deal is won)
- Mobile app
Copyright © 2025 Daniel Moreno. All rights reserved.
This is proprietary software. The source code is publicly visible for reference and portfolio purposes only. You may not use, copy, modify, distribute, or deploy this software — in whole or in part — without explicit written permission from the author.