Skip to content

German B2B industrial manufacturer · Manufacturing & industrial B2B

From three tools and a shared inbox to a single internal CRM — in five weeks

Replaced three legacy tools with one custom CRM and cut the quote cycle by 40%

5 weeks
From kickoff to UAT
3
Legacy tools retired
40%
Faster quote cycle
≈ €120k
Annual hours recovered

<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 &amp; 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 &amp; 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.

Your move

Let's make your software feel inevitable.

Tell us what you need. We reply within one working day — with a real opinion, not a sales pitch.