Our work / Case study
From seven manual uploads to one click, with an undo button
A self-service tool that moves thousands of regulated records between two CRMs, with preview, validation, a full audit trail and one-click rollback.
The problem
Our client holds and manages client money for tens of thousands of business customers. They run two customer-management platforms side by side: a mid-market cloud CRM and Salesforce. Downstream of those sit a customer portal and an accounting system. Records regularly have to move between the two CRMs, when a customer’s portfolio arrives from an outside provider or is transferred between customers.
Loading a portfolio into the older CRM meant uploading seven separate CSV files by hand, in a strict order, with a manual step halfway through to generate reference numbers. There was no preview, no check across the files, no undo and no record of who had imported what. Mistakes were cleaned up by hand. Moving data the other way, into Salesforce, had no tooling at all.
Our approach
We started with the operations team, mapping the existing process step by step with the people who ran it every day.
The key design decision was not to invent a new format. Another supplier’s automation already relied on the seven-file layout, so both directions of our tool produce and consume exactly those files. Everything downstream kept working unchanged, and the scope and risk stayed small.
- Exporter. Search for a customer, tick the records to move, and the tool gathers every related record, leaves out anything under active dispute, and produces the file set ready to load. A companion Salesforce-side exporter handles the opposite direction.
- Importer, as a guided workflow. Upload the files in any order and the tool works out which is which. It then validates references across all seven files and runs a dry run that marks every row as create, update, skip or error. Users can fix individual cells in the preview before anything is written. Committing runs about 14 dependency-ordered phases, including the CRM’s own reference-number generation.
- A ledger and an undo button. Every change is recorded with a “before” snapshot, so the import history shows exactly who did what, and any import can be rolled back in one click within 72 hours.
Delivery followed the same discipline. We tested in UAT first, every change went through a pull request, automated checks (about 470 tests) and deployment, and any production data fix was staged: dry run, one-record pilot, full apply, then verification.
Built with: Microsoft Azure (Functions, Storage Queues, Blob Storage, Key Vault, Application Insights, Entra ID single sign-on), Python, Salesforce (Apex, Lightning Web Components), GitHub Actions and REST APIs.
The result
- Seven manual uploads became one batch upload, with preview, validation, a full audit trail and one-click rollback.
- A route now exists for the direction that had none, from the older CRM into Salesforce.
- The operations team has used it in production since mid-2026. It replaced the previous middleware for bulk loads.
- It handles batches of thousands of records from a source system holding hundreds of thousands.
What made it hard
Duplicates when the cloud recycled a worker. The CRM doesn’t enforce unique references, so when the platform restarted a worker mid-job it created duplicate records. We now write an “attempted” checkpoint before any write and re-check the live system whenever a phase re-runs. Later re-runs have produced zero duplicates.
An undo that respects a cache and a finance system. The customer portal refreshes its copy of CRM data about once a minute, so simply deleting records left “ghost” entries visible to customers. Rollback now changes statuses first, waits for the portal to catch up, then deletes. Records that have already synced to the finance system are never touched.
Jobs that outlive their servers. Serverless hosting caps each run at a fixed time limit, and real batches take longer than that. After finding that the platform was quietly reclaiming “idle” instances mid-job, we moved from self-chaining requests to queue-driven continuation, with automatic retries and timed sweepers that restart anything that stalls.
What we’d do differently
We’d compare records created through the API against natively created ones from day one, build on queue-based orchestration from the start, and agree measurable success metrics with the client at kick-off.
Got data that has to move between systems safely? Book a call and tell us what you’re dealing with.