# NextCRM: a self-hosted CRM where soft delete and an audit diff are the default

> NextCRM is an open-source CRM on Next.js 16 and React 19 with PostgreSQL through Prisma, an invoicing module with Czech and English locales, AI enrichment running inside a cloud sandbox, and an MCP server exposing 127 tools. Records are soft deleted and every field change is diffed into an audit record.

**pdovhomilja/nextcrm-app** — NextCRM — Open-source CRM built with Next.js 16, React 19, PostgreSQL, Prisma 7, and shadcn/ui. CRM, projects, invoicing, documents, email client & AI features.

- Repository: https://github.com/pdovhomilja/nextcrm-app
- Website: https://demo.nextcrm.io
- Stars: 710 · Forks: 277
- Language: TypeScript
- License: MIT
- Published: 2026-09-14 · Updated: 2026-09-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/pdovhomilja-nextcrm-app

## 127 MCP tools over 15 modules, with search where it matters

The newest feature is a built-in Model Context Protocol server, which means agents such as Claude, Cursor or a custom agent read and write CRM data directly instead of going through a screen.

The surface is uniform, which is what makes it usable. Accounts, Contacts, Leads, Opportunities and Targets each expose six operations, list, get, search, create, update and delete. Products, Contracts and Activities expose five, dropping search. Documents exposes eight because it adds upload, download, link and unlink to the usual five. The headline figure is 127 tools across 15 modules.

Documents being the odd one out is the interesting detail: file handling is where a generic CRUD wrapper stops being enough, so that module grew its own verbs instead of pretending an upload is a create.

The result is that an agent's blast radius is the same as a user's, with the same validation, since the tools go through the same application layer rather than around it.

## Enrichment moved from an API to a sandbox with a real browser

Contact and target enrichment used to run through Firecrawl's API. It now runs inside an E2B cloud sandbox, a full Linux environment with a real Chrome, because the targets worth enriching are JavaScript-heavy pages, public LinkedIn profiles and paginated results that a single API call cannot reach.

Claude Sonnet 4.6 drives the research as a tool-use loop with five tools: `browser_open`, `browser_snapshot`, `browser_click`, `browser_extract` and `web_search`. Given only a company name, the agent is expected to find discoverable C-level contacts and create contact records automatically, skipping research it does not need, such as domain discovery when a website is already known.

Three guardrails make that safe enough to run unattended. Fields scoring below 0.6 confidence are discarded, only empty target fields are overwritten, and each target has a five-minute timeout after which partial results are still applied. Company enrichment then fans out, with each discovered contact enriched by its own Inngest job.

## Keys resolve through three tiers, and Firecrawl is still listed

The point of the key system is that the application runs with no keys in `.env` at all. Resolution order is stated in the README as a single line:

```
ENV variable  →  Admin system-wide  →  User profile
(highest)                              (lowest)
```

An admin sets system-wide keys for OpenAI, Firecrawl, Anthropic and Groq at `/admin/llm-keys`, encrypted at rest with AES-256-GCM. A user can set their own at `/profile?tab=llms`. When nothing is available at any tier, the enrichment buttons show an informative dialog instead of failing silently, which is the graceful-degradation path stated in the README.

One inconsistency survives the move away from Firecrawl. Enrichment now runs in the E2B sandbox, yet Firecrawl is still one of the four providers in the admin panel and `FIRECRAWL_API_KEY` is still in the example environment file, where its own comment still describes it as the contact enrichment key. There is also a schema migration behind this: the old `openAi_keys` table is replaced by a new `ApiKeys` table, and the README tells you to run `pnpm prisma migrate deploy`.

## Migrations refuse to run against a remote database

The database scripts are guarded, and the guard is unusual enough to be worth reading. Every one of `db:migrate`, `db:seed` and `db:reset` runs `db:guard` first, which is a shell script that asserts the connection string points at localhost. The example environment file explains why: the Prisma CLI reads only `.env`, never `.env.local`, so remote databases belong in that file, and the guard exists so a mistyped line cannot run a migration against a production database.

The rest of the script set is careful in the same way. `db:wait` polls `pg_isready` up to 60 times with a one second sleep, and its failure message names the usual causes and tells you to check the compose service state. `db:reset` removes the container and its volume before rebuilding from migrate and seed.

One script stands out: `build` runs `prisma generate`, then `prisma migrate deploy`, then `next build`. A build therefore applies migrations, which is convenient in a container and worth knowing before a build ever runs against a shared database.

## Three service containers, and one of them cannot be probed

docker-compose brings up the dependencies a Next.js CRM cannot avoid. PostgreSQL comes from `pgvector/pgvector:pg17`, which is where the vector search feature gets its extension, with a `pg_isready` healthcheck every five seconds. MinIO provides object storage with a curl-based liveness probe. Inngest runs as a dev server pointed at the app's Inngest API route.

The Inngest entry carries the most interesting comment in the file. It has no healthcheck because the image ships no HTTP client at all, no curl, wget or nc, so any exec-based probe fails permanently. Instead the app depends on it with `service_started`, and the comment argues why that is safe here: the dev server polls the app for functions rather than the other way round.

That is the kind of dependency-ordering decision that quietly breaks when someone swaps the image for a production one, so it is worth reading the comment before changing it. Default credentials in the file are `nextcrm` and `changeme`, which is fine locally and nothing else.

## Soft delete plus a diff engine is the audit design

Every CRM entity tracks its full change history, and the mechanism is two small decisions rather than a framework. Records are never hard deleted: a `deletedAt` column hides them from normal queries while preserving the row. A `diffObjects` utility computes before-and-after differences and stores them as structured JSON inside the audit record, so history is a queryable table rather than a log of prose.

The user-facing pieces are a per-entity history tab built from `AuditTimeline` and `AuditEntry` components, and an admin audit log at `/admin/audit-log` with filters across every entity plus restore support for soft-deleted records.

Activities work the same way. All five entity detail pages carry an Activities tab with notes, calls, emails, meetings and tasks, paginated with compound cursor pagination on `createdAt` plus `id` for stable ordering, and linked through `crm_ActivityLinks` so one call can reference both a contact and an opportunity.

## Invoicing is the newest module and it is Czech-aware

The invoicing module covers a full lifecycle: create, issue, pay, duplicate and cancel, with four document types, invoice, credit note, proforma and receipt. Lines carry quantity, unit price, discount percentage and tax rate, and the totals are automatic.

The tax engine is configurable for rates such as VAT and GST, with a per-line breakdown and summary buckets, and numbering is handled by an invoice series with a configurable prefix and suffix, the example being `INV-2026-0001`. Currencies are admin-managed and formatted locale-aware through `next-intl`, and the module ships full English and Czech translations.

Two implementation choices are worth copying. Only drafts are editable, enforced by permission guards on the status lifecycle `DRAFT` to `ISSUED` to `PAID`, `PARTIALLY_PAID` or `CANCELLED`, and writes go through Next.js server actions with Zod validation rather than an API route in the middle. Delivery is by Resend with a React Email template, PDFs are generated server-side at `/api/invoices/[id]/pdf`, and every status change lands in the activity log with an actor and a timestamp.

## Conclusion

NextCRM fits a small sales team that wants a self-hosted CRM with an audit trail and an invoicing workflow in the same application, since the soft delete, the diff engine and the status guards are built in rather than bolted on. It does not fit a team that wants a small deployment, because the stack is Next 16, React 19, PostgreSQL with pgvector, MinIO and Inngest running together. Before you deploy it, read the three service containers and the build-time placeholder environment variables, decide whether your enrichment runs in the E2B sandbox, and note that migrations refuse to run against a remote database until you change that guard script.

## FAQ

### What is NextCRM?

An open-source CRM built with Next.js 16, React 19, TypeScript, PostgreSQL through Prisma 7 and shadcn/ui. It covers CRM records, project management, invoicing, document storage, an email client, AI features, vector search and an MCP server for agent access.

### How does NextCRM run AI enrichment?

Inside an E2B cloud sandbox with a real browser, where Claude Sonnet 4.6 drives a tool-use loop with browser_open, browser_snapshot, browser_click, browser_extract and web_search. Fields scoring below 0.6 confidence are discarded, only empty fields are overwritten, and each target has a five-minute timeout after which partial results still apply.

### How do I supply API keys to NextCRM?

Through three tiers, in order: an environment variable, then admin system-wide keys set at /admin/llm-keys and encrypted at rest with AES-256-GCM, then a user's own keys at /profile?tab=llms. When no key exists at any tier, the enrichment buttons show an informative dialog.

### Can AI agents read NextCRM data?

Yes. It ships an MCP server exposing 127 tools across 15 modules, so agents such as Claude or Cursor can read and write CRM data directly. Most modules expose list, get, search, create, update and delete, and documents add upload, download, link and unlink.

## Sources

- [License: MIT](https://github.com/pdovhomilja/nextcrm-app/blob/main/LICENSE)
- [pdovhomilja/nextcrm-app on GitHub](https://github.com/pdovhomilja/nextcrm-app)
- [Project website](https://demo.nextcrm.io)
- [README](https://github.com/pdovhomilja/nextcrm-app/blob/main/README.md)
- [Releases](https://github.com/pdovhomilja/nextcrm-app/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pdovhomilja-nextcrm-app
