Engagements

The work, and how it actually goes

Sixteen engagements I take on regularly — what the situation usually looks like, how I'd approach it, and the part that tends to cause trouble. A few include the case for not doing the project at all, because that's sometimes the right answer.

These describe project types rather than specific clients. The patterns, constraints and failure modes come from a decade of enterprise architecture work; client engagements themselves stay confidential. On a call I can go considerably deeper on anything relevant to your situation.

Cloud & Modernization
Product teams, in-house development, software vendors· 4–6 weeks

Setting up DevOps from nothing, for a .NET and Angular team

The situation

The team writes good code and ships it badly. The API gets published from Visual Studio by whoever is around, the Angular build runs on one developer's machine, and the person who knows the deployment steps is the constraint on every release. Connection strings sit in appsettings.json in the repository. Nobody can say with certainty which commit is currently in production, and rollback means restoring a backup and hoping.

How I'd approach it

Start with the parts that unblock everything else. A branching model the team will actually follow — for a small team that's usually short-lived branches off trunk, because Gitflow's ceremony costs more than it returns below a certain headcount. Then the build: restore, test, publish artifacts for the API and the SPA in one pipeline. The Angular app gets built once and configured at runtime, so the identical artifact promotes from staging to production rather than being rebuilt per environment — rebuilding per environment means the thing you tested is not the thing you shipped. Infrastructure moves to Bicep, secrets to Key Vault accessed through managed identity, and database migrations become a deliberate gated step in the release rather than something that happens on app startup.

What usually bites

Two things, reliably. The first is EF Core migrations running automatically at startup — fine on one instance, a race the moment you scale out, and impossible to roll back cleanly under pressure. The second is that the test suite you want to gate the pipeline on frequently does not exist yet, and a quality gate that everybody learns to override is worse than no gate at all. That needs to be said in the first week, because it changes what the engagement can honestly promise.

What I'd cut first

Full infrastructure-as-code coverage on day one. Get the application pipeline working against the infrastructure that already exists, then bring environments under Bicep one at a time. Blue-green deployments, multi-region and automated performance gates all come later, if ever.

When I'd tell you not to

A two-person team releasing once a month to a single environment can get most of the benefit from a documented script and a checklist. Pipelines earn their keep when release frequency or team size makes coordination the bottleneck — I'd rather say that than build ceremony nobody needs.

Azure DevOps / GitHub ActionsBicep.NET 8AngularAzure App ServiceKey VaultManaged IdentityApplication Insights
Cloud & Modernization
Manufacturing, internal line-of-business apps· 6–8 weeks

Getting a monolith onto Azure without rewriting it

The situation

An application that runs the business and that nobody wants to touch. .NET Framework 4.x on a Windows Server VM, SQL Server sitting on the same box, deployed by remoting in and copying files. The developer who built it left years ago. Patching is a scheduled ordeal, and the disaster recovery plan is realistically a backup job someone hopes is still running.

How I'd approach it

Resist the rewrite. The goal for this phase is to get the thing somewhere you can deploy to safely and recover from — nothing more. Retarget the framework, pull connection strings and secrets into App Configuration and Key Vault, move session state out of process memory into Redis so it can run on more than one instance, and lift the database to Azure SQL. Then it goes onto App Service behind a pipeline. Modernization comes after, once deploying stops being frightening.

What usually bites

It is never the framework version that holds this up. It is the small local assumptions — writes to a path on the C: drive, Windows integrated auth, an SMTP relay that only accepts connections from that subnet, and a handful of jobs living in Task Scheduler that nobody documented. A real portion of the timeline goes to finding those, and the estimate should say so.

What I'd cut first

The microservices conversation. It comes up in every kickoff for this kind of project and it does not belong in this phase.

.NETAzure App ServiceAzure SQLKey VaultAzure Cache for RedisGitHub Actions
Cloud & Modernization
Platforms with multiple delivery teams· 8–12 weeks for the first extraction

Decomposing a .NET monolith, one seam at a time

The situation

Different project from a lift to Azure, and worth doing only for a concrete reason — several teams contending over one deployment, a module whose load profile looks nothing like the rest, or a release cadence held hostage by the slowest-moving part of the codebase.

How I'd approach it

Strangler pattern, not a rewrite. Find seams along transaction boundaries rather than along nouns — 'Customer' is a tempting service and usually a bad one. Extract exactly one, put API Management in front so callers don't need to know the logic moved, and keep the monolith as system of record until the seam proves out under real traffic. CQRS with MediatR inside the extracted service where the read and write paths genuinely diverge; not everywhere, because it costs clarity when applied by default. Service Bus for async work, correlation IDs from the first commit.

What usually bites

The shared database. Every microservices project is a database decomposition project in disguise, and the moment two services need the same table inside one transaction, the seam was drawn in the wrong place — that's a signal to redraw it, not to reach for a distributed transaction.

What I'd cut first

Extracting a second service in phase one. One, properly, with the operational tooling around it. Then decide whether the second is still worth it.

When I'd tell you not to

If none of those three pressures apply, the monolith is fine. I've talked more than one team out of this, and the money went further elsewhere.

.NET 8Azure API ManagementAzure Service BusAKSCQRS / MediatRApplication Insights
Cloud & Modernization
Retail, finance, operations reporting· 6–10 weeks

Migrating ETL packages nobody has looked at in years

The situation

A server running SSIS or Informatica workflows, orchestrated by SQL Agent, with a couple of jobs triggered by a recurring reminder in somebody's calendar. Failure notification consists of a person noticing a report looks stale — usually after the meeting where the report was used.

How I'd approach it

Inventory before anything else. In estates of this age a meaningful fraction of the workflows are dead and can be switched off after a watching period, which is the cheapest win available. Lift the survivors via the SSIS Integration Runtime to buy breathing room, then rewrite the ones carrying real business weight as native Data Factory pipelines with Mapping Data Flows and proper alerting. Business logic too gnarly for a data flow goes into a custom activity rather than being contorted into one.

What usually bites

Undocumented ordering dependencies between packages, and hardcoded UNC paths pointing at a share that has been migrated twice. Both tend to surface at cutover rather than during analysis, which is what the rehearsal is for.

What I'd cut first

Rewriting the full estate in one pass. Migrate what earns it, lift the rest, revisit in six months with real usage data.

Azure Data FactorySSIS Integration RuntimeAzure Data Lake Gen2Mapping Data FlowsAzure Monitor
Cloud & Modernization
Operations that run around the clock· 5–8 weeks

Moving a database to Azure SQL without a maintenance window

The situation

An on-premises SQL Server that needs to be in Azure, and a business that cannot take an outage — either because operations run continuously or because the only quiet window falls during month-end close.

How I'd approach it

Assess compatibility first with Data Migration Assistant. Cross-database queries, linked servers and SQL Agent jobs are the usual blockers, and they decide whether the target is Managed Instance or Azure SQL Database — a choice that is expensive to reverse. Then Database Migration Service in online mode with continuous sync. Encryption at rest and private networking get configured during the build, not retrofitted, because retrofitting them means a second migration. The cutover is rehearsed at least twice against a copy of production, and so is the rollback.

What usually bites

Stored procedure volume drives the timeline more than data volume does. A large procedure estate needs review, not just replication, and any plan that treats a thousand-table database and a two-hundred-table database as the same amount of work is not a plan.

What I'd cut first

Schema improvements. Move as-is, verify, then improve. Changing two variables during a cutover makes any problem twice as hard to diagnose at two in the morning.

Azure SQL Managed InstanceDatabase Migration ServiceData Migration AssistantPrivate EndpointsKey Vault
AI & Automation
Professional services, IT, engineering teams· 5–7 weeks

Making the document estate answer questions, so new hires stop asking people

The situation

A new engineer's first fortnight is mostly reading. Runbooks, architecture notes, SOPs, a SharePoint site with a decade of sediment in it, and a lot of context that exists only in the heads of three senior people who get interrupted constantly. Nobody can point to where the current version of anything lives.

How I'd approach it

Retrieval over the existing corpus rather than a fine-tune — AI Search for the index, Azure OpenAI for generation. Chunking that respects document structure instead of cutting every N characters, because a policy split mid-clause retrieves badly. Two non-negotiables: every answer cites its source document so people can verify it, and retrieval honours the existing permission model so it cannot surface something a new joiner shouldn't see.

What usually bites

The corpus is always worse than anyone admits at kickoff. Three versions of the same policy, no owner on half the documents, PDFs that are scans of printouts. Retrieval quality turns out to be a content-hygiene problem wearing an AI costume, and that lands around week two.

What I'd cut first

A bespoke chat interface. Put it in Teams where people already are and spend the saved time on retrieval quality, which is what decides whether anyone uses it twice.

Azure OpenAIAzure AI SearchPythonFastAPIMicrosoft TeamsAzure Functions
AI & Automation
E-commerce, SaaS, B2B services· 4–6 weeks

Ticket triage that suggests rather than decides

The situation

Tickets land in a shared mailbox and a helpdesk queue. Someone experienced spends the first hour of every day reading and routing them, and a misroute quietly costs a day of response time. Volume has grown past the point where this is a reasonable use of a senior person's morning.

How I'd approach it

Classify against the categories that actually exist in ticket history, not the ones on the org chart — those two lists are rarely the same. The model proposes a queue and a priority; the agent accepts or overrides in one click, and every override is logged as training signal. Confidence thresholds matter more than headline accuracy: routing a clear majority of tickets well and leaving the ambiguous remainder to a human beats routing everything adequately.

What usually bites

Label quality in historical data. Tickets get reassigned, closed under the wrong category, or merged, and the export reflects that mess. Part of this project is deciding what ground truth even is. It is also worth being blunt with whoever sponsors it: deflection reduces routine load, and the tickets left for humans get harder on average, so per-agent throughput does not rise the way a headline number implies.

What I'd cut first

Automated reply drafting. Triage has clear value and a small blast radius when wrong. Drafting follows once there is trust in the classifier.

Azure AI LanguageAzure OpenAIPythonFastAPIAzure FunctionsService Bus
AI & Automation
Distribution, logistics, manufacturing· 5–8 weeks

Invoice processing that hands off the hard cases instead of guessing

The situation

Accounts payable keys invoices by hand. Supplier formats vary — some PDFs, some scans, a few arriving as photographs taken on a phone. Month-end is a crunch, and the error rate climbs exactly when volume does.

How I'd approach it

Azure Document Intelligence, with a custom model trained on the handful of supplier layouts covering most of the volume and the prebuilt invoice model as fallback for the long tail. Vendor, amount, GST and PO number get extracted and validated; anything under the confidence threshold goes to an exceptions queue rather than into the accounting system. Writes happen through the ERP or Tally API — never by driving the UI, which breaks silently at the next vendor update.

What usually bites

Extraction is largely solved. The project actually lives in purchase order matching — tolerances, partial deliveries, line items that don't correspond one to one. That's where the weeks go, and it belongs in the scope conversation rather than in week four. Worth noting too that throughput gains are bounded by invoice volume, not by system capacity; the honest measure is hours returned to the finance team, not theoretical documents per day.

What I'd cut first

Handwritten annotations and non-standard attachments. Get the clean majority flowing and let the exceptions queue absorb the rest.

Azure Document Intelligence.NET CoreEF CoreTally / ERP APIAzure Blob StorageAzure SQL
AI & Automation
Multi-system operations, services firms· 4–6 weeks

Killing the monthly pack that takes two days to assemble

The situation

A management pack built by hand from five or six systems, consuming most of two days of an analyst's time. Occasionally two slides in the same deck disagree about the same number, which nobody catches until someone senior does.

How I'd approach it

Define each metric once, in one place, and compute it there. Scheduled overnight refresh into a warehouse, a semantic layer that owns the definitions, and generated commentary written over the computed figures. The language model writes narrative; it never calculates. That boundary is the whole design and it should be enforced in code rather than by convention. Alerting on threshold breaches goes to the channel people already watch.

What usually bites

Nobody agrees what an active customer is. Or a qualified lead, or the cutoff for a closed month. Reconciling those definitions is the actual project, and it is a governance exercise needing a decision-maker in the room — not just a data owner. Expect the reporting build to wait on it.

What I'd cut first

Real-time refresh. A nightly pipeline serves a monthly decision perfectly well, and building for real-time costs far more than the freshness is worth. Anyone promising both a nightly pipeline and real-time data is describing two different systems.

Azure Data FactoryAzure SynapsePower BIAzure OpenAIPython
AI & Automation
B2B SaaS, services, agencies· 3–5 weeks

Qualifying inbound leads before a human reads them

The situation

Inbound enquiries arrive from web forms, a shared inbox and social channels. Someone reads each one, decides whether it's worth pursuing, and books the good ones. Response time stretches across evenings and weekends, and the enquiries that arrive on a Friday night are the ones most likely to go cold.

How I'd approach it

Enrich the submission, score it against criteria the sales side actually uses rather than a generic framework, and route accordingly — straight to a booking link for the strong ones, a nurture sequence for the rest, and a human for anything the model is unsure about. Acknowledgement goes out immediately regardless of score, because speed of first response is the part with the clearest effect. Everything writes back to the CRM so scoring can be reviewed against what actually closed.

What usually bites

Scoring criteria are usually undocumented and partly instinct. Getting them written down means sitting with whoever currently does the qualifying and working through recent examples, including the ones they rejected. That conversation is most of the build.

What I'd cut first

Fully automated outbound sequences. Qualify and route first; automated multi-step outreach on top of an unproven score compounds any error in it.

When I'd tell you not to

Under roughly twenty enquiries a month, this doesn't pay for itself. A shared inbox rule and a booking link will do, and I'll say so.

Azure OpenAI.NET CoreCRM APIAzure FunctionsAzure SQL
AI & Automation
Agencies, professional services· 3–4 weeks

Onboarding that starts the moment a deal closes

The situation

A signed client waits several days for a welcome pack, workspace setup and a kickoff booking, because each step needs a person to remember it. The gap between signing and first contact is where early doubt sets in, and it's entirely self-inflicted.

How I'd approach it

Trigger on the CRM stage change. Provision the workspace from a template, send the welcome sequence, and offer kickoff slots immediately. Scheduled check-ins at set intervals with a short feedback prompt. The valuable part isn't the automation itself — it's that every client now gets the version of onboarding your best account manager would have delivered on a good week.

What usually bites

Templates go stale and nobody owns them. Six months on, new clients receive a welcome pack describing a service tier that no longer exists. Whoever owns the content needs to be named at handover, or this quietly degrades.

What I'd cut first

Personalised video and heavy branded assets in version one. Get the sequence reliable, then make it pretty.

.NET CoreCRM APIMicrosoft GraphAzure FunctionsAzure Logic Apps
Commerce & Integration
B2B suppliers selling into large enterprises· 6–10 weeks elapsed, less than half of it build

Punchout, so enterprise buyers can order without leaving their procurement system

The situation

A large customer says they can only buy through Ariba or Coupa, and procurement will not raise manual purchase orders. Without punchout the account either does not open or quietly shrinks to whatever can be bought off-contract. Someone forwards a copy of the cXML specification and it looks like a fortnight of work.

How I'd approach it

Three flows, not one. The setup request arrives server to server and is authenticated on a shared secret; the buyer then shops in your catalogue under an identity derived from that request; the cart returns as a form post through their browser. Separately — and this is the part that gets missed — the actual purchase order arrives later as a cXML OrderRequest once their internal approval clears. Buyer-specific contract pricing and catalogue restriction usually need building too, because a storefront written for public retail pricing cannot express either.

What usually bites

The edit operation. Buyers reopen requisitions to change them, and the setup request comes back carrying the previously returned lines. Rebuilding that cart from current prices rather than the prices originally quoted produces a silently different requisition and breaks the buyer’s approval trail. It is almost never caught before joint UAT, which is the most expensive place to find it.

What I'd cut first

Live inventory in the punchout catalogue. Approval takes days regardless, so stock at requisition time is not the number that matters, and the dependency buys nothing. Handle availability when the order actually arrives.

When I'd tell you not to

If one customer is asking and the contract is small, a hosted catalogue file may satisfy them for a fraction of the effort. Punchout earns its cost when several enterprise buyers want it, or when the account is large enough that procurement friction is costing real revenue.

cXML.NETAriba / CoupaAzure App ServiceAzure SQLApplication Insights
Commerce & Integration
Multi-tenant B2B commerce, wholesale, distribution· 12–16 weeks

Modernizing a multi-tenant commerce platform while it keeps trading

The situation

A platform serving many customers from one codebase, running on .NET Framework with a front end nobody wants to touch. It works, it earns, and it cannot be paused. Integrations have accumulated around it — payment gateways, identity, procurement, a handful of third-party APIs of varying quality — and each one is a reason the rewrite everyone keeps proposing never starts.

How I'd approach it

Move in slices, with the integration boundaries as the seams. Retarget to .NET and lift configuration and secrets out of the codebase first, then replace the front end area by area in Angular behind the existing routes so tenants migrate a screen at a time rather than overnight. Tenant isolation gets revisited deliberately during this — it is the one thing that is far cheaper to correct mid-modernization than after — and identity moves to OAuth with the older mechanism running alongside until traffic has actually shifted.

What usually bites

Tenant-specific behaviour that was never designed, only accumulated. Conditional logic keyed on a customer identifier, a config flag that one tenant depends on, a report only one account uses. Every such branch is a migration risk and none of them are documented. Finding them is discovery work that belongs in the estimate rather than in week six.

What I'd cut first

Redesigning the UI while re-platforming it. Port the screens as they are, get tenants onto the new stack, then improve the interface as a separate piece of work — otherwise every rendering difference becomes an argument about whether it is a bug or the new design.

.NETAngularOAuth / OIDCAzure App ServiceAzure SQLAzure Front Door
Custom Software
SaaS and product teams with EU or multi-jurisdiction users· 6–10 weeks

Building the data-rights machinery a privacy regime actually requires

The situation

A product that collects personal data and was built before anyone had to think about it. The data sits in the main database, and also in application logs, analytics events, a support tool, an email platform, a spreadsheet somebody exported eighteen months ago, and backups nobody has inventoried. Then a deletion request arrives, or a prospect sends a security questionnaire, and the honest answer to “where does personal data live” turns out to be nobody’s job to know.

How I'd approach it

Discovery before building anything. Map where personal data genuinely resides — including where it has leaked to, which is almost always logs, analytics payloads and third-party processors rather than the tidy columns someone expected. Then build the mechanics: subject access export, deletion that reaches every store rather than just the primary table, consent capture that records what was agreed and when, retention rules enforced by a scheduled job instead of by a policy document, and an audit trail that can evidence what happened. Where deletion conflicts with a legitimate need to retain — financial records being the usual case — the answer is pseudonymisation, and which fields that covers is a decision for your counsel, not for me.

What usually bites

Backups are the genuinely hard problem. A deletion request runs headlong into immutable backup media, and the workable answer is normally documented rotation with deletion reapplied on restore — but that is a position your lawyer signs off, not one an engineer should be inventing. The other reliable surprise is free text. Users type personal data into support tickets, internal notes and description fields, and no schema-level mapping catches it.

What I'd cut first

A self-serve privacy dashboard for end users. Handle requests through a documented internal process first and automate intake once volume justifies it — the obligation is to respond properly within the window, not to respond through a portal.

When I'd tell you not to

If you don’t yet have a lawyer or DPO engaged, start there rather than with me. I build the mechanisms that carry out a legal position; I don’t determine what that position should be, and any technical supplier who offers to decide your lawful basis is selling something they can’t stand behind.

.NET 8AngularAzure SQLAzure FunctionsKey VaultAzure Monitor
Custom Software
FMCG distribution, wholesale, dealer networks· 10–14 weeks

A CRM for a distributor, because the off-the-shelf ones don't fit

The situation

The business runs on spreadsheets and an accounting export. Generic CRMs have been trialled and abandoned, usually for the same reason — they model a sales pipeline well and a dealer hierarchy badly. Territory rules, slab-based schemes, credit limits and channel pricing all end up back in Excel because the CRM couldn't express them.

How I'd approach it

.NET Core API with an Angular front end. The scheme and pricing engine is treated as the core domain and built first with real test coverage, because it is exactly what generic products get wrong and what the business will judge the system on. Role-based access reaching down to territory. Real-time stock visibility where reps need it. Accounting integration runs as a scheduled sync rather than a live dependency, so a failure there doesn't take the CRM down with it.

What usually bites

Scheme logic carries exceptions that live in the sales head's memory and surface one at a time during UAT. Getting them written down before build starts is most of the discovery work and worth being stubborn about. The second risk is adoption — reps who have tracked their own retailers in a personal spreadsheet for years do not hand that over readily, and that needs managing as a change problem, not a training one.

What I'd cut first

A native mobile app. A responsive build covers the field requirement well enough to learn what a mobile app would actually need to do.

.NET 8AngularEF CoreAzure SQLSignalRTally integrationAzure App Service
Custom Software
HVAC, installation, maintenance, field operations· 8–12 weeks

A field service app that works where there's no signal

The situation

Technicians work off paper job cards. Data reaches the office days later, sometimes illegibly, and dispatch decisions get made against a picture of the world that is already stale. Connectivity at actual sites ranges from patchy to nonexistent.

How I'd approach it

Offline-first as an architectural decision rather than a feature — a local store with a queued sync, and conflict resolution rules agreed with the business before any code is written, because those rules are a business decision engineers shouldn't be making alone. Photo capture with client-side compression, since site photos over a weak connection will otherwise sink the sync. Digital signature on completion, push notification on assignment, and a .NET Core API behind it all.

What usually bites

Adoption, more than architecture. If completing a job in the app takes more taps than the paper card, technicians keep using the card and fill the app in from the van at day's end — which produces worse data than paper did. Timing the workflow against the paper form in week one is not optional, and it usually forces a redesign of at least one screen.

What I'd cut first

Dispatch and route optimization. Get accurate job data flowing first; optimizing a schedule built on bad inputs is not worth much.

.NET 8Angular PWAIndexedDBAzure MapsAzure Notification HubsAzure Blob Storage

What drives the number

Why two similar projects quote differently

Four variables account for most of the difference. Understanding them is also how you reduce the number — several are things you control.

How many systems have to talk

One system in, one system out is straightforward. Each additional integration adds its own auth, its own failure modes, and its own vendor documentation of variable quality. This is the single biggest driver on most projects.

The state of the data

Clean, consistently structured source data makes a build predictable. Twelve years of inconsistent records, duplicate masters and undocumented columns turns a five-week project into an eight-week one — and that work happens whether or not it was scoped.

Which decisions are already made

If your team already agrees what an active customer is, or how a sync conflict resolves, the build starts immediately. If not, that agreement has to be reached first, and it needs a decision-maker rather than an engineer.

Who takes it over

Handing to an internal team who will maintain it is cheaper than building something that has to run untouched for two years. The latter needs more documentation, more defensive handling, and more thought about the failure modes nobody will be watching for.

I quote a fixed price after the mapping session, not before. An estimate given without seeing your systems is a guess with a margin baked in for being wrong, and you end up paying for that margin whether or not it was needed. Indicative starting points for each type of engagement are on the pricing page.

The engagement model

Four phases, no surprises

Same shape whether it's a three-week automation or a fourteen-week application build. Each phase has a defined output you can hold me to.

01

Map the actual problem

A working session on your process — where time goes, where errors creep in, what a fix is worth. You leave with a written opportunity map whether or not we go further.

→Opportunity map + rough effort estimate
02

Scope one narrow thing

One workflow, one measurable outcome, fixed price. Deliberately small — a scope you can judge in weeks beats a roadmap you have to take on faith.

→Fixed-price proposal + success metric agreed upfront
03

Build in the open

Weekly working software, not status decks. You see the real thing in your own environment and redirect while it's still cheap to redirect.

→Working system in production, deployed to your infra
04

Hand over properly

Documentation, a walkthrough with your team, and source in your repository. You are never locked into me — that constraint keeps the work honest.

→Docs, handover session, full source ownership

What you own at the end

Full source code in your own repository. Infrastructure in your own cloud account, under your billing. Documentation written for the next engineer, not for me. No proprietary runtime, no per-seat licence, no dependency on my continued involvement.

If you want ongoing support afterwards, that's a separate conversation — never a condition of the build.

Recognise your situation in one of these?

The mapping session is free and yields something useful even if we never work together.

Book a Free Consultation