No description
  • TypeScript 97.8%
  • JavaScript 1%
  • Shell 0.5%
  • CSS 0.5%
  • Dockerfile 0.1%
Find a file
Jesse van Mullem b9ba887daa feat(ui): footer met versie, build-datum en ontwikkelaar
Toont "Vinculum v2.0.0 (build 0) | Gebouwd op 16 juli 2026 | Ontwikkeld
door Je-Ma ICT Beheer" onder elke pagina, in zowel de v1- als v2-layout.

Buildnummer is de patch-component (v1.1.12 = build 12). Een oplopend
nummer of commit-SHA kan niet: .git staat in .dockerignore.

Build-datum via nieuwe build-info.json, geschreven tijdens docker build.
Niet hergebruikt uit security-report.json omdat die bij een falende npm
audit terugvalt op een stub met lege generatedAt — dan zou de datum
verdwijnen terwijl de build gelukt is. Stub-eerst-patroon wel overgenomen
zodat een falend script de COPY in de runner-stage niet breekt.

Ontwikkelaar-naam hardcoded, niet via getBranding(): die vendorName is het
bedrijf dat de app draait, dus een klant-deploy zou "Ontwikkeld door Acme
BV" tonen.

In de v2-layout verhuist de pb-24 van main naar de footer, anders valt die
op mobiel achter de fixed BottomTabV2.
2026-07-16 14:43:03 +02:00
deploy HSTS-header in de app + ML-KEM/post-quantum documentatie bij de TLS-config 2026-05-22 16:49:02 +02:00
docs sec(docs): CRON_SECRET-rotation-script + secret-rotation-procedures 2026-06-30 14:14:42 +02:00
mobile fix(mobile): timer-knop compact op klein scherm + safe-area-padding header 2026-06-16 22:48:20 +02:00
ops fix(ops): Coolify v4 draait niet via systemd — update restore-script tekst 2026-06-17 13:12:39 +02:00
prisma feat(auth): admin-WebAuthn naast password+TOTP + login-flow combo-form 2026-06-30 16:56:45 +02:00
public feat(push): test-knop voor action-knoppen + iOS-waarschuwing 2026-06-18 08:33:56 +02:00
scripts feat(ui): footer met versie, build-datum en ontwikkelaar 2026-07-16 14:43:03 +02:00
src feat(ui): footer met versie, build-datum en ontwikkelaar 2026-07-16 14:43:03 +02:00
.dockerignore Add web-based CRM with Coolify deployment 2026-05-18 22:40:57 +02:00
.env.example release: v2.0.0 2026-07-16 14:11:27 +02:00
.gitignore feat(ui): footer met versie, build-datum en ontwikkelaar 2026-07-16 14:43:03 +02:00
CHANGELOG.md feat(ui): footer met versie, build-datum en ontwikkelaar 2026-07-16 14:43:03 +02:00
DEPLOY.md docs: documentatie herschreven in het Engels, README opnieuw opgezet 2026-06-11 14:26:59 +02:00
docker-compose.override.yml revert(compose): db-service terug in main docker-compose.yaml 2026-06-30 14:48:47 +02:00
docker-compose.yaml revert(compose): db-service terug in main docker-compose.yaml 2026-06-30 14:48:47 +02:00
docker-entrypoint.sh feat(deploy): license-defaults + auto-secrets voor nieuwe instances 2026-06-16 16:42:00 +02:00
Dockerfile feat(ui): footer met versie, build-datum en ontwikkelaar 2026-07-16 14:43:03 +02:00
next.config.ts infra: docker-compose split + typecheck script + TS-baseline doc 2026-06-30 14:20:58 +02:00
package.json feat(ui): footer met versie, build-datum en ontwikkelaar 2026-07-16 14:43:03 +02:00
postcss.config.mjs Add web-based CRM with Coolify deployment 2026-05-18 22:40:57 +02:00
prisma.config.ts chore: alle uitleg-comments uit de codebase gestript 2026-06-11 14:07:12 +02:00
README.md chore: rebrand OpenCRM -> Vinculum 2026-06-16 11:17:23 +02:00
tailwind.config.ts Email templates, auto reminders, dark theme, shortcuts and audit log 2026-05-20 15:01:44 +02:00
tsconfig.json Revert Prisma 7-pogingen — terug naar werkend Prisma 6.19 2026-05-24 15:29:50 +02:00

Vinculum

CRM and billing for a small IT service shop, in one Docker container on your own server. Customers, quotes with digital signatures, invoices with iDEAL payment links, bookkeeping with receipt OCR, domain and DNS management, mail hosting, a service desk pipeline that drafts DNS changes from tickets, and offsite backups. No SaaS fees, your data stays in your own PostgreSQL.

Built by Je-Ma ICT Beheer for daily use in production at the host configured via APP_URL. Current version: see CHANGELOG.md.

Contents

  1. What it does
  2. Quick start
  3. Stack
  4. Configuration
  5. Local development
  6. Deployment
  7. Scheduled jobs
  8. Backups and restore
  9. Security
  10. Documentation

What it does

The full path from lead to paid invoice, plus the operational side of running an IT shop:

Lead > Deal > Quote (DocuSeal) > Project > Hours > Invoice > Mollie > Paid
                                Contracts . Files . Vault . Assets . DNS . Mail

Sales and customers. Customers with contacts, tags, notes, attachments and VAT/KvK data. Lookup against the Dutch trade register fills address details automatically. A Kanban pipeline for deals, an AI summary per customer, asset and license tracking with expiry reminders, an NPS survey with a public link, and a credential vault per customer (encrypted with AES-256-GCM, every reveal audited with IP and browser).

Quotes and contracts. Line items, VAT, currencies, versioning and a product catalog. An AI generator drafts quote lines or a complete Dutch contract from a short description. Signing runs through DocuSeal: for quotes above 5000 euro ex VAT and for all contracts, every partner flagged as signer plus the customer signs in order. When the last signature lands, the invoice is created, a Mollie payment link is attached and the mail goes out automatically.

Invoices and bookkeeping. Multiple currencies, PDF with logo and a payment QR that opens Mollie checkout, automatic reminders, recurring invoices, UBL export, bank statement import with automatic matching, and a quarterly VAT report. Purchases come in by photo or PDF and Claude reads supplier, date, amounts and VAT off the receipt. Above a threshold an approval flow kicks in: every approver gets a magic link by mail and the purchase is only booked when everyone has signed off. A monthly ZIP with all invoices, receipts and CSV exports goes to the accountant by mail. Incoming supplier invoices can also be ingested straight from an IMAP mailbox.

Domains, DNS and mail. Domains are linked to customers and their records are managed live against ClouDNS or your own WHM server. Openprovider handles registration and transfers, including availability checks and per customer contact handles. Mailcow integration activates mail for a domain in one click: mail domain, DKIM key and the matching DNS records (MX, SPF, DKIM) are created together. Every record change triggers a notification mail and lands in the audit log.

Service desk automation. A cron polls Jitbit tickets and lets Claude classify them: DNS change, new mailbox, domain registration or link request. Each proposal lands in an approval queue with a confidence score, the parsed change and a prepared reply. An operator approves, and the change is applied and confirmed to the customer on the ticket. When information is missing, the system mails the customer a clarification question written by Claude and picks the thread back up when they reply. Unknown senders can be offered a secure onboarding form: the customer fills in company details that are encrypted in the browser before they ever reach the server.

WordPress and hosting operations. Sites per customer with a cPanel connection, traffic reports generated from AWStats data with AI written commentary, published to a reports subdomain and mailed to the customer. A maintenance log, file sharing per site, and a CMDB for hardware with QR labels and depreciation tracking.

Telephony. Rinkel integration: click to call from any customer page, call history synced into the activity feed, and a popup for incoming calls.

Productivity. Cmd+K palette, keyboard shortcuts, dark mode, installable as a PWA with a receipt scan shortcut, and an audit log of every write.

All AI features are optional and run on the Anthropic API. Every external integration switches itself off when its environment variables are empty; the core CRM keeps working.

Quick start

git clone https://code.vanmullemholding.eu/jesse-a/Vinculum.git crm
cd crm
cp .env.example .env        # fill in at least AUTH_SECRET and DATABASE_URL
docker compose up --build   # builds and starts app + postgres

Open http://localhost:3000. On first boot the container creates an admin account from SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD. Leave the password empty and a strong random one is generated and printed once in the container log, so there is never a publicly known default.

Stack

Layer Choice
Framework Next.js 16, App Router, server actions, Turbopack
Language TypeScript
Database PostgreSQL with Prisma 6
Cache / locks Redis, optional (rate limits, webhook replay cache, cron locks)
Styling Tailwind CSS
PDF pdf-lib
Mail nodemailer over SMTP
Auth JWT sessions via jose, bcryptjs, TOTP per RFC 6238
AI Anthropic SDK (Claude)
Payments Mollie
Signing DocuSeal, HMAC verified webhooks
DNS ClouDNS API and WHM API, behind one provider abstraction
Domains Openprovider REST API
Mail hosting Mailcow API
Helpdesk Jitbit API
Telephony Rinkel API
Storage local volume or any S3 compatible bucket

The container entrypoint runs prisma db push on boot, creates the admin account if the database is empty, and starts the standalone Next.js build on port 3000. There are no manual migration steps.

Configuration

Everything is configured through environment variables. The minimum to boot:

DATABASE_URL="postgresql://user:pass@db:5432/crm"
AUTH_SECRET="a random string of at least 32 characters"
APP_URL="https://crm.example.com"
SEED_ADMIN_EMAIL="you@example.com"
SEED_ADMIN_PASSWORD=""        # empty = generated and shown once in the log
COMPANY_NAME="Your Company"

.env.example documents every variable, grouped per integration: SMTP, Mollie, Anthropic, DocuSeal, ClouDNS, WHM, Openprovider, Mailcow, Jitbit, Rinkel, IMAP intake, KvK lookup, S3 uploads and S3 backups. A feature without its variables simply stays hidden in the UI.

Note on AUTH_SECRET: it is not just the session signing key. Encryption keys for the credential vault, TOTP secrets and API tokens are derived from it with HKDF. Store it in your password manager as well; a database backup is useless without it.

Local development

nvm use 24
npm install
docker compose up -d db     # postgres only
npm run dev                 # next dev server on :3000

Schema changes go through npx prisma db push during development, and npx prisma studio gives you a data browser. Seed data lives in prisma/seed.ts and runs with npx prisma db seed.

Deployment

The reference setup is Coolify v4 with Traefik in front. Short version:

  1. Create a PostgreSQL database in Coolify (or use a managed one).
  2. New application, build pack Dockerfile, port 3000, attach your domain.
  3. Set the environment variables.
  4. Add a persistent volume on /app/data (or set STORAGE_BACKEND=s3).
  5. Deploy. Pushes to the linked branch deploy automatically.

DEPLOY.md is the full walkthrough, including the health check, TLS hardening (modern ciphers only, HSTS, post quantum key exchange via X25519MLKEM768), offsite backups and the dependency update workflow.

Scheduled jobs

All cron endpoints accept only POST with an Authorization: Bearer $CRON_SECRET header and are safe to retrigger: every job takes a distributed lock and the work itself is idempotent. Set them up as scheduled tasks in Coolify:

Endpoint Suggested schedule What it does
/api/cron/payment-reminders daily 09:00 first and second invoice reminder
/api/cron/recurring-invoices daily 07:00 create and send subscription invoices
/api/cron/backup daily 03:15 dump database and uploads to S3
/api/cron/security-check daily 06:00 npm audit, mails admins on HIGH or CRITICAL
/api/cron/jitbit-dns-prep every 5 to 10 min classify new tickets into change proposals
/api/cron/inkoop-mail-import every 15 min import supplier invoices from the IMAP mailbox
/api/cron/openprovider-expiry daily sync domain expiry dates and nameservers
/api/cron/rinkel-cdr-import every 15 min pull call history into activity feeds
/api/cron/wordpress-healthcheck hourly check linked WordPress sites

Webhooks live under /api/webhooks/docuseal (HMAC required), /api/webhooks/mollie (status is refetched from Mollie, the body is never trusted) and /api/webhooks/rinkel/<token>.

Backups and restore

One button under settings, or the cron endpoint, streams a pg_dump and a tar of the uploads directly to any S3 compatible bucket (DigitalOcean Spaces, Backblaze B2, Cloudflare R2, AWS). No temp files on disk. Status and the last result are visible on the settings page.

Restore: stop the app, restore the SQL dump with psql, unpack the uploads tar into the volume, start the app. The exact commands are in DEPLOY.md, step 9. Test a restore once before you need it.

If you run on a managed database (the production setup uses DigitalOcean Managed PostgreSQL with automatic backups and point in time recovery), the builtin database backup becomes a second line rather than the primary one.

Security

Security has had more attention than any other part of this codebase: four external review rounds plus an Internet.nl pass, 45 findings fixed and the trail documented. A white box source review of v1.0.0 plus an external probe of the live deployment (June 2026) rated the posture Strong, with zero critical and zero high findings. The full report is in SECURITY-ASSESSMENT-2026-06.md.

The short list of what is in place:

  • 2FA with TOTP is mandatory for every user, with recovery codes and replay protection. Sessions are validated against the database on every request and can be revoked instantly, per session or everywhere at once.
  • Secrets at rest (vault entries, TOTP secrets, API tokens) use AES-256-GCM with per purpose keys derived from AUTH_SECRET through HKDF. Reveals are audited; some secrets are write only and can never be read back.
  • A strict Content Security Policy with a fresh nonce per request and no unsafe-inline, plus HSTS, frame denial and the rest of the modern header set.
  • Outbound requests to user influenced hosts go through an SSRF defense layer: string checks, DNS resolution with private IP rejection, and a fetch wrapper that refuses redirects.
  • Webhooks verify HMAC signatures with constant time comparison and a freshness window. Cron endpoints require a bearer secret. Public links use random tokens of 160 bits or more, never sequential IDs.
  • Password changes, TOTP changes and email changes require the current password again, with a rate limit.

Reports from the community are welcome at security.txt on the production host.

Documentation

Document What
DEPLOY.md step by step deployment on Coolify, TLS, backups, updates
docs/user-guide.md guide for daily users, no technical background needed
SECURITY-ASSESSMENT-2026-06.md the June 2026 security assessment report
docs/openprovider-research.md API notes behind the Openprovider integration
docs/rinkel-research.md API notes behind the Rinkel integration
CHANGELOG.md every change, per commit, in Dutch

License

No open source license. It runs a real company, so the code is published as reference. Want to use it commercially? Ask first.

Made with a great many git push origin main.