Context
The project had to unify several stages of the sales cycle. The goal was not only to store contacts, but to make the full prospecting workflow readable inside one interface.
The need
The need went far beyond a polished interface. It required a single source of truth for leads, pipelines, commercial exchanges, email templates, users and external integrations.
The built response
A CRM with lead views, dashboards, team management, sales templates and an API layer able to connect the wider prospecting and enrichment stack.
What this project shows in practice
This CRM makes sales follow-up easier to read, centralizes leads and connects the key flows used for prospecting.
Key features
Interconnections
What this changes
What was hard, what we settled
Technical stakes
- Centralize scattered data (Google Sheets, Notion, emails, CSV exports) without breaking everything during migration
- Model a pipeline that adapts to several business workflows without becoming overengineered
- Build reliable near-real-time reporting without depending on a paid external BI tool
- Manage permissions per team, role and scope without blocking daily productivity
- Store full interaction history while keeping screens fast to load
Stack choices
- Next.js + TypeScript
Front and back in one repo, simple deployment, shared types between client and server. Ideal for an internal tool that has to evolve quickly.
- Postgres + Prisma
Complex relations (lead, deal, activity, user), clean analytical queries, traceable migrations. No scaling headaches at our volume.
- BullMQ + Redis
All heavy actions (exports, syncs, bulk emails) run in the background without freezing the interface.
- Resend for emails
Simple API, clean deliverability, linear pricing. Lighter than SES to set up, more honest than Sendgrid to manage.
- Hosted on our datacenter
Sensitive sales data, strict GDPR, full cost and security control. No surprises on the cloud bill.
Difficulties faced
Pipeline shapes change per business
Our first attempt was too rigid. We rebuilt the model so each team can define its own stages without touching code or duplicating views.
Keeping reporting snappy at 10K leads
Naive queries blew up in latency past a few thousand rows. We added precise Postgres indexes and pre-aggregated key metrics through Node cron jobs.
Email templates without becoming a Sendgrid clone
We wanted dynamic variables, open/click tracking and clean delivery without bolting on a heavy marketing platform. Resend + custom composers did the job.
Sync without silent overwrites
When two reps edit the same lead at the same time, we couldn't let one overwrite the other silently. We added optimistic versioning with manual conflict resolution when needed.
What we learned
- Start from the user views, not the data model. Otherwise you design a database nobody knows how to use.
- Reporting native to the tool > Looker on the side. If it sits inside the working screen, the team actually uses it.
- Store everything that can be historized. Data not collected today never comes back.
- Email templates must stay editable by sales, not locked inside the codebase.
A few useful views to show how the project comes together in practice.