<div class="not-prose relative my-12 overflow-hidden rounded-3xl border border-white/10 bg-ink-900/40 p-6 md:p-10 reveal">
<div aria-hidden="true" class="absolute inset-0 opacity-70" style="background: radial-gradient(closest-side at 75% 50%, rgba(232,149,74,0.20), transparent 70%);"></div>
<svg viewBox="0 0 880 260" class="relative h-auto w-full" role="img" aria-label="Three legacy tools merging into one CRM">
<defs>
<linearGradient id="crm-merge" x1="0" x2="1">
<stop offset="0%" stop-color="rgba(255,255,255,0.25)"/>
<stop offset="100%" stop-color="#E8954A" stop-opacity="0.9"/>
</linearGradient>
</defs>
<g transform="translate(30,30)">
<rect width="170" height="54" rx="10" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.15)"/>
<rect x="14" y="16" width="8" height="22" rx="1" fill="#6EE7E0" opacity="0.5"/>
<rect x="26" y="22" width="8" height="16" rx="1" fill="#6EE7E0" opacity="0.7"/>
<rect x="38" y="12" width="8" height="26" rx="1" fill="#6EE7E0" opacity="0.6"/>
<text x="60" y="25" fill="#F5EFE6" font-size="12" font-weight="500">Access DB</text>
<text x="60" y="39" fill="rgba(255,255,255,0.5)" font-size="9" font-family="ui-monospace, monospace" letter-spacing="0.12em">2011 · LEGACY</text>
</g>
<g transform="translate(30,110)">
<rect width="170" height="54" rx="10" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.15)"/>
<g transform="translate(12,14)">
<rect width="24" height="26" fill="none" stroke="rgba(110,231,224,0.6)" stroke-width="1.2"/>
<line x1="8" y1="0" x2="8" y2="26" stroke="rgba(110,231,224,0.5)" stroke-width="1"/>
<line x1="16" y1="0" x2="16" y2="26" stroke="rgba(110,231,224,0.5)" stroke-width="1"/>
<line x1="0" y1="9" x2="24" y2="9" stroke="rgba(110,231,224,0.5)" stroke-width="1"/>
<line x1="0" y1="18" x2="24" y2="18" stroke="rgba(110,231,224,0.5)" stroke-width="1"/>
</g>
<text x="60" y="25" fill="#F5EFE6" font-size="12" font-weight="500">Shared Excel</text>
<text x="60" y="39" fill="rgba(255,255,255,0.5)" font-size="9" font-family="ui-monospace, monospace" letter-spacing="0.12em">PIPELINE.XLSX</text>
</g>
<g transform="translate(30,190)">
<rect width="170" height="54" rx="10" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.15)"/>
<g transform="translate(14,16)">
<rect width="26" height="22" rx="2" fill="none" stroke="rgba(110,231,224,0.6)" stroke-width="1.2"/>
<path d="M 0 0 L 13 12 L 26 0" fill="none" stroke="rgba(110,231,224,0.6)" stroke-width="1.2"/>
</g>
<text x="60" y="25" fill="#F5EFE6" font-size="12" font-weight="500">Shared Inbox</text>
<text x="60" y="39" fill="rgba(255,255,255,0.5)" font-size="9" font-family="ui-monospace, monospace" letter-spacing="0.12em">OUTLOOK</text>
</g>
<path d="M 210 57 C 360 57, 420 130, 560 130" stroke="url(#crm-merge)" stroke-width="2" fill="none"/>
<path d="M 210 57 C 360 57, 420 130, 560 130" stroke="#E8954A" stroke-width="2" fill="none" class="dash-anim" opacity="0.8"/>
<path d="M 210 137 L 560 130" stroke="url(#crm-merge)" stroke-width="2" fill="none"/>
<path d="M 210 137 L 560 130" stroke="#E8954A" stroke-width="2" fill="none" class="dash-anim" opacity="0.8"/>
<path d="M 210 217 C 360 217, 420 130, 560 130" stroke="url(#crm-merge)" stroke-width="2" fill="none"/>
<path d="M 210 217 C 360 217, 420 130, 560 130" stroke="#E8954A" stroke-width="2" fill="none" class="dash-anim" opacity="0.8"/>
<g transform="translate(560,60)">
<rect width="280" height="140" rx="18" fill="rgba(232,149,74,0.10)" stroke="rgba(232,149,74,0.55)" stroke-width="1.5"/>
<rect x="0" y="0" width="280" height="140" rx="18" fill="none" stroke="rgba(232,149,74,0.35)" stroke-width="1" class="pulse-soft"/>
<text x="140" y="46" text-anchor="middle" fill="#F5EFE6" font-size="24" font-family="inherit" font-weight="600" letter-spacing="-0.01em">One Internal CRM</text>
<text x="140" y="70" text-anchor="middle" fill="rgba(232,149,74,0.85)" font-size="10" font-family="ui-monospace, monospace" letter-spacing="0.2em">SINGLE LOGIN · SSO · AUDIT</text>
<g transform="translate(36,86)">
<circle cx="0" cy="0" r="4" fill="#E8954A"/>
<text x="14" y="4" fill="rgba(255,255,255,0.8)" font-size="11">Customers</text>
<circle cx="86" cy="0" r="4" fill="#E8954A"/>
<text x="100" y="4" fill="rgba(255,255,255,0.8)" font-size="11">Quotes</text>
<circle cx="170" cy="0" r="4" fill="#E8954A"/>
<text x="184" y="4" fill="rgba(255,255,255,0.8)" font-size="11">Orders</text>
</g>
<g transform="translate(36,110)">
<circle cx="0" cy="0" r="4" fill="#6EE7E0"/>
<text x="14" y="4" fill="rgba(255,255,255,0.8)" font-size="11">DATEV</text>
<circle cx="76" cy="0" r="4" fill="#6EE7E0"/>
<text x="90" y="4" fill="rgba(255,255,255,0.8)" font-size="11">Sage ERP</text>
<circle cx="170" cy="0" r="4" fill="#6EE7E0"/>
<text x="184" y="4" fill="rgba(255,255,255,0.8)" font-size="11">Outlook</text>
</g>
</g>
</svg>
<p class="relative mt-6 text-center text-xs uppercase tracking-eyebrow text-copper-400">Three tools in · one system out · five weeks</p>
</div>
The challenge
The client — a family-owned industrial manufacturer in North Rhine-Westphalia — had scaled to €38M in revenue on the back of a sales team that cared deeply about their customers and tracked almost everything in their heads. The supporting stack had drifted into chaos:
- A Microsoft Access database from 2011 with the customer master.
- A shared Excel file for the quoting pipeline, guarded by one sales ops manager.
- A shared Outlook inbox where everything that mattered eventually lived.
Quotes took an average of 11 days. Senior sales reps left with customer history nobody else could reconstruct. Two enterprise-CRM rollouts had been tried in the previous three years. Both ended with the team quietly reverting to the spreadsheet.
They didn't need Salesforce. They needed a tool that respected their actual process.
Our roadmap
<div class="not-prose relative my-12">
<ol class="relative space-y-6 md:space-y-8">
<li class="reveal group relative pl-14 md:pl-20" style="transition-delay: 0ms;">
<div aria-hidden="true" class="absolute left-[1.1rem] top-12 bottom-[-1.5rem] w-px bg-gradient-to-b from-copper-500/30 to-copper-500/5 md:left-[1.6rem]"></div>
<div class="absolute left-0 top-0 flex h-10 w-10 items-center justify-center rounded-full border border-copper-500/40 bg-ink-900 font-display text-sm font-semibold text-copper-300 shadow-[0_0_0_4px_var(--color-ink-950)] md:h-[3.25rem] md:w-[3.25rem] md:text-base">01</div>
<div class="absolute left-[2.05rem] top-[1.1rem] md:left-[2.9rem] md:top-6">
<span class="relative flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-copper-400 opacity-60"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-copper-400"></span>
</span>
</div>
<div class="rounded-2xl border border-white/10 bg-gradient-to-br from-ink-800/40 via-ink-900/60 to-ink-950/80 p-5 transition-all duration-300 group-hover:border-copper-500/40 group-hover:shadow-[0_16px_48px_-16px_rgba(232,149,74,0.3)] md:p-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<span class="font-mono text-[10px] uppercase tracking-[0.22em] text-copper-400">Phase 01</span>
<span aria-hidden="true" class="h-px w-6 bg-copper-500/30"></span>
<span class="text-xs uppercase tracking-eyebrow text-ink-300">Week 1</span>
</div>
<h3 class="mt-2 font-display text-lg leading-snug text-porcelain-100 md:text-xl">Architecture & data model</h3>
<p class="mt-3 text-sm leading-relaxed text-ink-300 md:text-base md:leading-relaxed">Mapped the real quote-to-order flow with sales, migrated Access into a versioned schema.</p>
</div>
</li>
<li class="reveal group relative pl-14 md:pl-20" style="transition-delay: 100ms;">
<div aria-hidden="true" class="absolute left-[1.1rem] top-12 bottom-[-1.5rem] w-px bg-gradient-to-b from-copper-500/30 to-copper-500/5 md:left-[1.6rem]"></div>
<div class="absolute left-0 top-0 flex h-10 w-10 items-center justify-center rounded-full border border-copper-500/40 bg-ink-900 font-display text-sm font-semibold text-copper-300 shadow-[0_0_0_4px_var(--color-ink-950)] md:h-[3.25rem] md:w-[3.25rem] md:text-base">02</div>
<div class="absolute left-[2.05rem] top-[1.1rem] md:left-[2.9rem] md:top-6">
<span class="relative flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-copper-400 opacity-60"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-copper-400"></span>
</span>
</div>
<div class="rounded-2xl border border-white/10 bg-gradient-to-br from-ink-800/40 via-ink-900/60 to-ink-950/80 p-5 transition-all duration-300 group-hover:border-copper-500/40 group-hover:shadow-[0_16px_48px_-16px_rgba(232,149,74,0.3)] md:p-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<span class="font-mono text-[10px] uppercase tracking-[0.22em] text-copper-400">Phase 02</span>
<span aria-hidden="true" class="h-px w-6 bg-copper-500/30"></span>
<span class="text-xs uppercase tracking-eyebrow text-ink-300">Week 2</span>
</div>
<h3 class="mt-2 font-display text-lg leading-snug text-porcelain-100 md:text-xl">Core flows</h3>
<p class="mt-3 text-sm leading-relaxed text-ink-300 md:text-base md:leading-relaxed">Customers, contacts, opportunities, quotes — built end-to-end with auth, audit log, SSO.</p>
</div>
</li>
<li class="reveal group relative pl-14 md:pl-20" style="transition-delay: 200ms;">
<div aria-hidden="true" class="absolute left-[1.1rem] top-12 bottom-[-1.5rem] w-px bg-gradient-to-b from-copper-500/30 to-copper-500/5 md:left-[1.6rem]"></div>
<div class="absolute left-0 top-0 flex h-10 w-10 items-center justify-center rounded-full border border-copper-500/40 bg-ink-900 font-display text-sm font-semibold text-copper-300 shadow-[0_0_0_4px_var(--color-ink-950)] md:h-[3.25rem] md:w-[3.25rem] md:text-base">03</div>
<div class="absolute left-[2.05rem] top-[1.1rem] md:left-[2.9rem] md:top-6">
<span class="relative flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-copper-400 opacity-60"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-copper-400"></span>
</span>
</div>
<div class="rounded-2xl border border-white/10 bg-gradient-to-br from-ink-800/40 via-ink-900/60 to-ink-950/80 p-5 transition-all duration-300 group-hover:border-copper-500/40 group-hover:shadow-[0_16px_48px_-16px_rgba(232,149,74,0.3)] md:p-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<span class="font-mono text-[10px] uppercase tracking-[0.22em] text-copper-400">Phase 03</span>
<span aria-hidden="true" class="h-px w-6 bg-copper-500/30"></span>
<span class="text-xs uppercase tracking-eyebrow text-ink-300">Week 3</span>
</div>
<h3 class="mt-2 font-display text-lg leading-snug text-porcelain-100 md:text-xl">Quote engine</h3>
<p class="mt-3 text-sm leading-relaxed text-ink-300 md:text-base md:leading-relaxed">Their configurable pricing rules, versioned quote PDFs, email-send from within the app.</p>
</div>
</li>
<li class="reveal group relative pl-14 md:pl-20" style="transition-delay: 300ms;">
<div aria-hidden="true" class="absolute left-[1.1rem] top-12 bottom-[-1.5rem] w-px bg-gradient-to-b from-copper-500/30 to-copper-500/5 md:left-[1.6rem]"></div>
<div class="absolute left-0 top-0 flex h-10 w-10 items-center justify-center rounded-full border border-copper-500/40 bg-ink-900 font-display text-sm font-semibold text-copper-300 shadow-[0_0_0_4px_var(--color-ink-950)] md:h-[3.25rem] md:w-[3.25rem] md:text-base">04</div>
<div class="absolute left-[2.05rem] top-[1.1rem] md:left-[2.9rem] md:top-6">
<span class="relative flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-copper-400 opacity-60"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-copper-400"></span>
</span>
</div>
<div class="rounded-2xl border border-white/10 bg-gradient-to-br from-ink-800/40 via-ink-900/60 to-ink-950/80 p-5 transition-all duration-300 group-hover:border-copper-500/40 group-hover:shadow-[0_16px_48px_-16px_rgba(232,149,74,0.3)] md:p-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<span class="font-mono text-[10px] uppercase tracking-[0.22em] text-copper-400">Phase 04</span>
<span aria-hidden="true" class="h-px w-6 bg-copper-500/30"></span>
<span class="text-xs uppercase tracking-eyebrow text-ink-300">Week 4</span>
</div>
<h3 class="mt-2 font-display text-lg leading-snug text-porcelain-100 md:text-xl">Integrations</h3>
<p class="mt-3 text-sm leading-relaxed text-ink-300 md:text-base md:leading-relaxed">DATEV, Outlook, the ERP (Sage), and a Zapier fallback for the long tail of legacy tools.</p>
</div>
</li>
<li class="reveal group relative pl-14 md:pl-20" style="transition-delay: 400ms;">
<div aria-hidden="true" class="absolute left-[1.1rem] top-12 bottom-[-1.5rem] w-px bg-gradient-to-b from-copper-500/30 to-copper-500/5 md:left-[1.6rem] hidden"></div>
<div class="absolute left-0 top-0 flex h-10 w-10 items-center justify-center rounded-full border border-copper-500/40 bg-ink-900 font-display text-sm font-semibold text-copper-300 shadow-[0_0_0_4px_var(--color-ink-950)] md:h-[3.25rem] md:w-[3.25rem] md:text-base">05</div>
<div class="absolute left-[2.05rem] top-[1.1rem] md:left-[2.9rem] md:top-6">
<span class="relative flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-copper-400 opacity-60"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-copper-400"></span>
</span>
</div>
<div class="rounded-2xl border border-white/10 bg-gradient-to-br from-ink-800/40 via-ink-900/60 to-ink-950/80 p-5 transition-all duration-300 group-hover:border-copper-500/40 group-hover:shadow-[0_16px_48px_-16px_rgba(232,149,74,0.3)] md:p-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<span class="font-mono text-[10px] uppercase tracking-[0.22em] text-copper-400">Phase 05</span>
<span aria-hidden="true" class="h-px w-6 bg-copper-500/30"></span>
<span class="text-xs uppercase tracking-eyebrow text-ink-300">Week 5</span>
</div>
<h3 class="mt-2 font-display text-lg leading-snug text-porcelain-100 md:text-xl">UAT & rollout</h3>
<p class="mt-3 text-sm leading-relaxed text-ink-300 md:text-base md:leading-relaxed">Two days of shoulder-to-shoulder UAT with every rep, then a staged rollout across 3 regional teams.</p>
</div>
</li>
</ol>
</div>
How we shipped it
Week 1 — Model the business, not your preferred framework
We blocked a full day with the sales director and the two most tenured reps. We whiteboarded the actual quote flow — with every weird exception (framework agreements, call-off orders, the one customer who insists on a fax confirmation). By lunch we had thrown away two of our default data-model assumptions. By evening we had a schema the team recognised as theirs.
The Access database was migrated into PostgreSQL the next day, with a versioned migration script and a deterministic diffing pass so we could re-run it right up to go-live.
Week 2 — Core flows, no half-finished screens
Customers → contacts → opportunities → quotes. Every entity had full CRUD, an audit trail, and role-based permissions from day one. SSO via the client's existing Azure AD, so reps didn't need a new password.
We shipped to a staging URL by end of week 2 with real migrated data. Reps could already log in and look up their own accounts.
Week 3 — The quote engine, which was the actual product
The client's pricing rules were not arbitrary — they encoded years of margin learning. We didn't try to reinvent them. We built a rule DSL that sales ops could edit without touching code, and we generated versioned quote PDFs straight from the database. Every quote the client ever sent is now reproducible byte-for-byte from its ID.
Week 4 — Integrations, the boring but necessary layer
- DATEV export for the accounting team — matched their existing workflow.
- Outlook add-in so reps could drag emails into a customer record without leaving their inbox.
- Sage ERP bridge — read-only for stock levels, write-on-accept for order creation.
- A thin Zapier escape hatch for the long tail of smaller tools we didn't want to hard-wire.
Week 5 — UAT, rollout, and one very important party
Two days of structured UAT with every rep, one region at a time. We watched them work, fixed 23 small things on the spot, and rolled out to production on the following Monday. The client threw a small party. We drank the Kölsch.
The architecture
A stack their in-house IT team could maintain — and take over — from day one.
- Backend: TypeScript on Node, Fastify, PostgreSQL, Prisma. Versioned migrations, per-row audit log.
- Frontend: Astro + React islands for interactive screens, server-rendered for speed.
- Auth: Azure AD SSO, per-role permissions in Postgres, MFA enforced.
- PDFs: Deterministic rendering pipeline — same inputs, same bytes, forever.
- Integrations: DATEV, Sage ERP, Microsoft 365, a small Zapier bridge.
- Infra: Single-region Hetzner VMs + managed Postgres, on-prem backup to the client's own NAS. No hyperscaler lock-in.
- Observability: Self-hosted Grafana, Loki logs, a simple error page that the ops manager can actually read.
Everything in a single git repository. One-command deploy. A README their IT lead could follow without calling us.
The results
Measured after the first full quarter in production:
- Quote cycle dropped from 11 days to 6.6 days — roughly 40% faster.
- Three tools retired on the same day we went live (Access, the spreadsheet, the shared inbox).
- New-rep onboarding from 6 weeks to 9 days. The CRM is now the training manual.
- ~€120k of recovered time per year — conservatively estimated on the minutes saved per quote across the team.
- Zero production incidents in the first 90 days. Two small bug fixes, both shipped the same day they were reported.
What's next
The next phase adds a customer portal — letting large accounts place call-offs directly and see live order status. Because we own the data model, not a vendor, that extension slots in naturally instead of requiring a re-platform.
If you have a sales or ops team running on a stack of tools that "kind of works" — and every CRM pitch has left you feeling like you'd be building for the software instead of the other way round — talk to us. We'll build software that respects the way your team actually works, and we'll ship it in weeks.