Three definitions of a person, one live platform.
Restructuring the customer data model of a live, synchronised CRM platform — without stopping it.
The situation
A global professional services organisation runs its client-relationship platform on Dynamics 365 Customer Insights – Journeys and Dynamics 365 Sales, deployed across five European markets and synchronised in both directions with a long-established in-house marketing system that remains the system of record. DynaVerto’s founder joined the programme in late 2023 and has led solution architecture and technical delivery ever since, working within a mixed internal–external team — a multi-year engagement that continues today.
The platform had inherited a flat contact model: one person, one record, one set of fields. Reality is less tidy. A person holds positions at several organisations, uses different email addresses in different roles, and gives consent per address — while sales thinks in people, marketing automation thinks in deliverable addresses, and the legacy system thinks in people with multiple roles. The compromises showed in daily work: a fragile “primary contact” flag deciding which address marketing could see, other addresses invisible to campaigns, consent stored per person when it is legally per address, and web forms creating duplicates that someone later merged by hand.
What we did
Instead of patching the flat model again, we rebuilt the data model underneath the live platform. Contacts became a normalised hierarchy — person → position (one per employer) → address-role record (one per email and role) → interests — with each market’s requirements expressed as configuration rather than code branches.
On top of that, person-centric and email-centric records were separated into two logical kinds on the same contact table, preserving compatibility with out-of-the-box segmentation, event registration and form matching. The two worlds connect through exactly one bridge and are guarded so that no plugin ever synchronises them directly. Consent was re-anchored to the email address — one authoritative record per unique address; the full model is its own story.
Delivery discipline carried the risk. Every new behaviour ships dark behind a feature switch that defaults to off, so activation is a configuration flip, not a deployment.
The migration ran in place, in phases: dry runs, iterative batches, protective guards, and a final set of six blocking validation gates — orphaned interest records, coverage against each market’s own rule, one main position per person, one main address record per group, interest linkage completeness, and overlay consistency between the person-level and address-level records. Each gate’s legitimate exceptions were defined up front rather than granted after the fact. The migration was executed end to end at production scale before rollout.
The gates were a precondition, not a report: outbound synchronisation to the system of record stayed switched off until all six came back clean, so nothing left the platform until the model underneath it was proven.
The phased shape was not the original plan — it was bought. The first full dry run created several thousand empty child records, because a guard that everyone assumed was suppressing the platform’s cascade logic during synchronisation had in fact never been active: it depended on a per-handler flag that was switched off on every registration. A related defect in the same family had already left several hundred contacts holding duplicate main records. Both had to be cleaned up before the model could be trusted, and the migration was redesigned around switching the cascade logic off for the load itself.
Two things were not solved. Contacts carrying a hundred or more positions still exceed the platform’s transaction limits and have to be processed individually, sometimes in pieces. And the record filtering holds across the standard views but cannot be enforced on user-owned personal views or advanced search — a bounded exposure that was accepted with its blast radius written down, rather than engineered around at disproportionate cost.
In five lines
- Normalised person → position → address-role hierarchy replacing a flat, single-address contact model
- Person-centric and email-centric records coexisting on one entity — sales and marketing each get the identity model they actually work in
- Consent anchored to the email address, with the full model covered in its own case study
- Every new behaviour behind a feature switch defaulting to off — activation and rollback are configuration, not code
- Six blocking validation gates held outbound synchronisation switched off until every one came back clean
Results
- 40,000+ contacts restructured in a single migration pass, producing roughly 75,000 position and address-role records — all six blocking validation gates clean before outbound sync was switched back on, zero unresolved integrity violations
- One data model across five markets, with country differences held in configuration instead of code forks
- Consent legally aligned to the address it belongs to, with a working double-opt-in path per market
- Marketing sees every deliverable address; sales keeps one record per person; the fragile primary-flag workaround is no longer load-bearing
All engagements are anonymised. References available on request, subject to client approval.
Restructuring a data model under a live platform?
Staring at a similar migration? Book a call — we’ll tell you what it actually took.