Writing a CRM migration you can run twice

Idempotent imports, reconciliation reports, and why the dry run is the actual deliverable. With the reconciliation query I hand to finance teams.

A migration script that only works once is not a migration script. It is an ambush waiting for cutover weekend.

If running the script a second time creates duplicates, fails on foreign keys, or leaves half-imported entities with missing relationships, the team will spend the entire migration window debugging partial state rather than validating data integrity.

The dry run is the deliverable

In every CRM migration I run, the dry run is the actual deliverable. The cutover is merely the replay of a solved problem.

If you cannot run your import script against a refreshed staging database on Thursday afternoon without manual intervention, you are not ready for a Friday night cutover.

Idempotence by design

Every entity record needs a deterministic identifier derived from the source system. If a record already exists with that external key, update it or pass over it without throwing an unhandled exception.

Reconciliation reports for finance

Never tell a client the migration succeeded because the import logs completed with zero exit codes. Produce a reconciliation summary comparing source record counts and balance totals against the destination before asking anyone to sign off.

Finance teams do not care about API endpoints or batch chunking sizes. They care about two numbers: did every active customer arrive, and does the ledger match to the penny?

I run this query against the staging import and hand the result directly to the finance lead:

SELECT 
  src.external_id,
  src.company_name,
  src.source_balance,
  dst.dest_balance,
  ROUND((dst.dest_balance - src.source_balance), 2) AS variance
FROM staging_accounts src
LEFT JOIN crm_accounts dst 
  ON dst.source_system_ref = src.external_id
WHERE src.is_active = TRUE
  AND (dst.dest_balance IS NULL OR (dst.dest_balance - src.source_balance) != 0);

If that query returns even a single row with a non-zero variance, the migration does not proceed to production. Finding a data mapping mismatch in staging on a Wednesday takes twenty minutes to fix; finding it after a live weekend cutover can freeze invoicing for a month.

Rollback and cutover

Cutover weekend should be the most uneventful part of the entire project. If engineers are working into the early hours of Sunday morning writing ad-hoc SQL updates or manually re-linking orphaned contacts, the preparation was inadequate.

A robust cutover requires an explicit decision gate:

  1. Replay verification: Run the full migration script against Friday evening’s delta extract.
  2. Automated reconciliation: Execute the verification suite across record counts, parent-child relationships, and balances.
  3. The go or no-go milestone: At twelve o’clock on Saturday, review the reconciliation output with the project sponsor.

Because the script is idempotent, rolling back does not mean untangling broken records. If an unforeseen edge case appears in the delta extract, the rollback decision is simple: abort the destination sync, leave the legacy CRM active for Monday morning, and resolve the transformation rule in staging during normal working hours.

When cutover is treated as the repeatable replay of a solved problem rather than a high-stakes gamble, the organisation transitions without drama, lost data, or emergency weekend heroics.

READ NEXT
FOR LEADERS · 6 MIN

What your IT support contract is probably still charging you for

FOR LEADERS · 4 MIN

Nobody owns your roadmap. That is the actual problem