May 12, 2026

Four tools, three constraints, one small dev team

How a three-person dev team at a Copenhagen consultancy decided what to build, what to buy, and what to hybridise, and why those decisions weren't really about preferences.

ArchitectureProcessInternal toolsConsulting

Four years ago I joined a Copenhagen consultancy as the only full-time developer on a team of three. By the time I left, we had shipped or rolled out four different tools to fix one underlying mess.

One was a custom web app written in Node.js, Express, and React, self-hosted on a VPS. One was an off-the-shelf low-code CRM. One was a third-party HR system we didn't build or configure, just bought. One was an Excel-and-VBA workbook that grew into the company's operating dashboard.

Each of those was the right call. None of them would have been right in a different lane.

This post is about the three constraints that decided every one of those choices, and why "build it" is almost never the answer in isolation.

The mess we started with

The company ran on Microsoft Access. The database had worked for years. Several team members reached it through Remote Desktop sessions, each with their own desktop and their own quirks. It wasn't broken so much as friction-laden. Two people in the same table at the same time was a small disaster. Schemas drifted. The shared spreadsheets that orbited Access (the project-status sheet, the time-tracking files, the scheduling docs) were on a constant copy-paste loop with the database.

Around it, the company had a folder-and-Word-document HR system on the same remote desktops, employee hours scattered across Excel files, a CRM workflow that lived inside Access, and a project status workflow nobody could quite see across in real time.

The CTO wasn't looking for one tool. He was looking for the friction to stop.

The three constraints

Every tool decision we made passed through three filters. We didn't always articulate them as a list, but in retrospect they were the only thing that mattered.

Team capacity. I was the only full-time developer, plus two students who worked part-time and rotated as they finished degrees. The honest budget for any custom build was small. A six-month internal project meant nothing else got done that year.

Microsoft ecosystem alignment. The company lived inside Microsoft 365. Teams, Outlook, OneDrive, Azure AD. Any tool that didn't integrate with Microsoft Graph and Azure AD single sign-on was a non-starter. A separate login meant a separate audit surface and a separate place for IT to manage permissions.

ISO and GDPR posture. The company was working toward ISO 27001. Anything that touched personal data (employees, clients, time records) needed to either self-host or have a real data-processing agreement. SaaS that lived in someone else's bucket without that paperwork was out.

Three filters. Every candidate tool got rated against all three. The reason there are four different solutions in this story is not that we couldn't decide. It's that no single tool clears all three filters for every kind of problem a 100-person consultancy has.

State of Affairs: hybrid as the actual answer

The first thing I shipped wasn't even an application. It was an Excel-and-VBA workbook. Or rather, it started as one. It grew into the company's operating dashboard.

The project-status spreadsheet was the document the entire delivery team lived in. Nobody was going to give it up. But it was being maintained by copy-pasting tables out of Access every Monday morning, and an older macro someone had written years before to format the import had stopped working when Access changed schemas.

I wrote a new one. Then I kept writing.

Over time the workbook turned into what people inside the company called the State of Affairs. The architecture was a master overview sheet pointing into a generated forest of per-thing sheets: one per consultant, one per project, one per task, one per project lead, one for training and coaching. Each kind of sheet had a template. A "Create" button on the overview ran a VBA macro that imported the latest report and schedule data from two helper workbooks, then regenerated every sheet from its template and rebuilt the hyperlinks back to the overview.

That was the spine. On top of it sat admin-editable sheets for committed hours and price points per client, plus formula-driven views of meetings sold per hour, calls per hour, and the rest of the metrics leadership wanted to see. Hours data fed in from Dialogue Time once that existed. Schedule data fed in from the existing scheduling export.

Nobody outside the company would have called it software. They would have been wrong. The leadership team opened it every morning to see whether the previous week had made money.

The hybrid worked because the constraint wasn't "Excel is bad." It was "the copy-paste cycle is bad." Once the data flow was solved by code, Excel did the rest of the job well: pivot tables, formulas, conditional formatting, charts. None of that needed reinventing in a custom UI.

Once Dialogue Time was running, we added an Excel.js plugin so users could push hours straight from Dialogue Time into State of Affairs with a single button. The macro got the company past the Access copy-paste era. The plugin moved it into the custom-app era, with hours flowing in live on demand. The spreadsheet itself never moved.

That tool is still in use. The data layer is heading toward Power BI now that the rest of the stack is heading there. The underlying pattern is the same as it was on day one: keep the spreadsheet, fix the data flow into it.

Dialogue Time: when custom was the only option

The time-tracking and scheduling problem didn't have a Microsoft-ecosystem off-the-shelf answer in 2021. Microsoft has Project, but Project is for project plans, not consultant hours. There were SaaS time-tracking tools, but most lived outside Microsoft Graph, and none of them handled internal scheduling the way the company needed.

So we built Dialogue Time. PHP first, because that was fastest. Single sign-on via Microsoft Graph from day one. No parallel login.

Eighteen months later we rebuilt it from scratch in Node.js, Express, and React, self-hosted on a VPS with Apache fronting the app and a MySQL database behind it. The trigger was Microsoft Teams: the company wanted the tool to live as a Teams extension, in the same panel where conversations happened, so consultants weren't switching to a separate website to log hours. The rebuild was slower than expected. Some of the PHP features came back in degraded form for a release or two, and we took real internal pushback for that. The fear was reasonable. When you're a small team building a tool the whole company depends on, every dropped feature feels like a strategic blunder.

It wasn't. The React version eventually overtook PHP and added what PHP couldn't easily do: an office desk booking module, written during COVID so the company could see how many people were in on a given day and stay under the legally allowed cap. That module took two weeks. It would have taken months in PHP.

This was the lane where custom was the only choice. No off-the-shelf option fit all three filters. We had the team capacity for it because the workflow was central to billing, which made the investment justifiable.

Ninox: when off-the-shelf wins on its own terms

When it came time to retire the CRM workflow out of Access, the question was whether to extend Dialogue Time or buy a CRM.

We bought.

Ninox came in because it passed all three filters. It self-hosted, which gave us the GDPR posture we needed. It supported Single Sign-On through Microsoft, which kept us inside one identity provider. And it's a low-code CRM, so the team could configure new fields and views without my involvement. That freed up the small dev team for problems that actually needed code.

I could have built a custom CRM. With three people, it would have meant a six-month sidetrack. The right call wasn't to demonstrate I could build a CRM. It was to recognise that the constraints that made Dialogue Time custom (no SSO-ready scheduling tool exists) didn't apply here. Ninox exists. It fits.

Ninox is still in use today. That's the tell that the call was right.

Implee: when you don't even configure

The HR mess was different again. Employee records, contracts, holiday logs, performance notes. All of it lived in folders of Word documents on the remote desktops. Personal data scattered across drives that were never going to pass an ISO audit.

This was the easy decision. We didn't build, and we didn't configure. I went to Implee's sales team, sat through their demo, mapped how their data model handled the company's workflow, and recommended we buy it. No code. The team's job here was procurement and onboarding.

Buying software with no in-house lift was the cheapest thing we did that year, and arguably the highest-impact.

The thesis

The interesting tension in build-versus-buy conversations isn't "custom is better" or "off-the-shelf is better." It's that the right answer depends on a small number of constraints most teams don't articulate until after they've spent money on the wrong thing.

For us at the consultancy the constraints were team size, Microsoft ecosystem, and compliance posture. A different company with a different stack might have different constraints. The number is usually three or four, and they're worth writing down before any product evaluation starts.

Inside those constraints, the framework is uninteresting:

  • No off-the-shelf option clears the filters → build custom. Dialogue Time.
  • An off-the-shelf option clears the filters → buy. Ninox, Implee.
  • The off-the-shelf workflow is good but the data pipeline into it is broken → patch with a hybrid layer. State of Affairs.

What's hard isn't the deciding. It's having the discipline to check the constraints rather than defaulting to whichever flavour you personally prefer.

If you want help mapping that out for your own stack, the custom internal tools service page covers how I'd run that exercise, and the Dialogue Time case study has more depth on the custom side specifically.

Case study

Replaced scattered timesheets and manual payroll processes with a single source of truth. Consultants log hours in one system, managers approve in real-time, and exports are audit-ready by default.

Full-stackInternal toolsIntegration
Case study

Replaced cash handling at the company canteen with NFC tap-to-buy. Employees tap badges, purchases sync to payroll, and the canteen eliminated cash entirely.

Full-stackInternal toolsIntegration
Writing

The steps I use to go from a sentence-long idea to a scoped, buildable MVP with realistic trade-offs.

ProductArchitectureProcess
Writing

Phase 1, 2, 3 beats "big bang". Better for budget, clarity, and the sanity of everyone involved.

ProcessDeliveryConsulting