Work  /  Lead & Sales  /  16  ·  Home services & construction

CRM Rebuild & Migration, with the Automations on Top

Home services · Agencies · B2B services · Real estate

Built — counted from the workflow JSON in the repository; not yet run against live accounts

80nodes
5workflows
20sticky notes
84connections
Records moving from an old database to a new one, watched by a dry-run check.

The problem

You cannot automate on top of a data model that does not exist.

The CRM is a spreadsheet, or three. Or it is a HubSpot nobody configured, and the real state of every deal is in one person’s inbox. Every automation proposal dies there.

How it runs

  1. Audit

    01 / 06 Workflow 1 of 5 · Source Profiler & Audit · 13 nodes

    Fill rate per field, distinct values and duplicate clusters — measured before anything is designed.

  2. Dedupe and map

    02 / 06 Workflow 2 of 5 · Dedupe + Mapping Engine · 19 nodes

    Identities are normalised, pairs scored and merged, and every record mapped to the agreed object model.

  3. Dry run

    03 / 06 Workflow 3 of 5 · Migrate (Dry Run + Live) · 17 nodes

    The migration runs with its dry-run flag on by default, and emits the exact payload it would write — and every field it would leave empty.

  4. Live, with a rollback log

    04 / 06 Workflow 3 of 5 · Migrate (Dry Run + Live) · 17 nodes

    Each rollback row is reserved before its record is created and confirmed after it.

  5. Roll back, if needed

    05 / 06 Workflow 4 of 5 · Rollback Executor · 12 nodes

    Undoing a run means typing ROLLBACK- and that run’s own ID.

  6. Automate on top

    06 / 06 Workflow 5 of 5 · Routing + Stage Follow-Up · 19 nodes

    Intake, grading by a rule table anyone can read, and a 3/5/7/14/21/30-day follow-up ladder that stops on a reply.

The five workflows, as built

Every node with its icon, as on the n8n canvasstarts a runHover a node for its name and type

  • 01Source Profiler & Audit

    13 nodes · 14 connections · 5 notes

    Start - Manual · Manual TriggerStart - ManualStart - Weekly Schedule · Schedule TriggerStart - WeeklyScheduleLoad Config · Edit FieldsLoad ConfigRead Leads Raw · Google SheetsRead Leads RawRead Contacts Raw · Google SheetsRead Contacts RawRead Deals Raw · Google SheetsRead Deals RawMerge Leads + Contacts · MergeMerge Leads +ContactsMerge + Deals · MergeMerge + DealsProfile Fields & Duplicate Candidates · CodeProfile Fields &Duplicate…Route Report Rows · SwitchRoute Report RowsAppend Field Usage Report · Google SheetsAppend FieldUsage ReportAppend Duplicate Clusters Report · Google SheetsAppend DuplicateClusters ReportInsert Audit Run Summary · PostgresInsert Audit RunSummary

    fill rates, distinct values, duplicate clusters

  • 02Dedupe + Mapping Engine

    19 nodes · 21 connections · 5 notes

    Start - Manual (Run After Audit Reviewed) · Manual TriggerStart - Manual(Run After Audit…Load Config · Edit FieldsLoad ConfigRead Leads Raw · Google SheetsRead Leads RawRead Contacts Raw · Google SheetsRead ContactsRawTag Entity: Leads · Edit FieldsTag Entity:LeadsTag Entity: Contacts · Edit FieldsTag Entity:ContactsMerge Person Pool · MergeMerge PersonPoolNormalize Identity Fields · CodeNormalizeIdentity FieldsGuard: Has Identifier? · IfGuard: HasIdentifier?Score Candidate Pairs · CodeScore CandidatePairsTag - No Identifier (Needs Review) · CodeTag - NoIdentifier…Route Scored Output · SwitchRoute ScoredOutputInsert Dedupe Decisions Log · PostgresApply Auto-Merge · CodeApply Auto-MergeAppend Needs-Review Queue · Google SheetsTag Distinct Records · CodeTag DistinctRecordsRecombine Ready Records · MergeRecombine ReadyRecordsMap Source Fields -> Object Model · CodeMap SourceFields -> Object…Upsert Into migration_staging · PostgresUpsert Intomigration_staging

    normalise, score, merge, map, stage

  • 03Migrate (Dry Run + Live)

    17 nodes · 17 connections · 4 notes

    Start - Manual · Manual TriggerStart - ManualLoad Migration Config · Edit FieldsLoad MigrationConfigRead Staging Rows · PostgresRead Staging RowsValidate Against Object Model · CodeValidate AgainstObject ModelRoute Valid vs Invalid · IfRoute Valid vsInvalidAppend Validation Failures · Google SheetsAppend ValidationFailuresNotify — Rows Blocked By Validation · SlackNotify — RowsBlocked By…Dry Run? · IfDry Run?Build Diff Report · CodeBuild Diff ReportWrite Dry-Run Diff · Google SheetsWrite Dry-RunDiffDry Run Complete — Nothing Written · No OperationDry Run Complete— Nothing WrittenBatch For Live Write · Loop Over ItemsBatch For LiveWriteReserve Rollback Row · PostgresReserve RollbackRowCreate In Destination CRM · HTTP RequestCreate InDestination CRMConfirm Rollback Row · PostgresConfirm RollbackRowFinalise Run · PostgresFinalise RunNotify — Live Migration Complete · SlackNotify — LiveMigration…

    validate, then diff or write, with a rollback log

  • 04Rollback Executor

    12 nodes · 13 connections · 3 notes

    Start - Manual · Manual TriggerStart - ManualLoad Rollback Config · Edit FieldsLoad RollbackConfigConfirmation Phrase Matches? · IfConfirmationPhrase Matches?Refused — Confirmation Did Not Match · No OperationRefused —Confirmation Did…Read Rollback Log For Run · PostgresRead Rollback LogFor RunBatch Rollback Entries · Loop Over ItemsBatch RollbackEntriesHas A Destination Id? · IfHas A DestinationId?Delete From Destination CRM · HTTP RequestDelete FromDestination CRMMark Reverted · PostgresMark RevertedLog Unresolved For Manual Check · Google SheetsLog UnresolvedFor Manual CheckSummarise Rollback · PostgresSummariseRollbackNotify — Rollback Complete · SlackNotify — RollbackComplete

    a confirmed undo; the unresolved go to a person

  • 05Routing + Stage Follow-Up

    19 nodes · 19 connections · 3 notes

    New Lead Webhook · WebhookNew Lead WebhookNormalise Inbound Lead · CodeNormalise InboundLeadRecent Duplicate Lookup · PostgresRecent DuplicateLookupAlready Known? · IfAlready Known?Attach Activity To Existing · PostgresAttach ActivityTo ExistingRespond — Matched Existing · Respond to WebhookRespond — MatchedExistingGrade And Assign Owner · CodeGrade And AssignOwnerCreate In CRM · HTTP RequestCreate In CRMRecord New Lead · PostgresRecord New LeadNotify Owner · SlackNotify OwnerRespond — Created · Respond to WebhookRespond — CreatedFollow-Up Schedule · Schedule TriggerFollow-UpScheduleRead Deals Due A Step · PostgresRead Deals Due AStepSelect Template For Step · CodeSelect TemplateFor StepAnything Due? · IfAnything Due?Nothing Due · No OperationNothing DueBatch Sends · Loop Over ItemsBatch SendsSend Follow-Up · GmailSend Follow-UpRecord Step Sent · PostgresRecord Step Sent

    intake, grading, the 3/5/7/14/21/30 ladder

80 executing nodes, by type

Postgres 15Code 12Google Sheets 11If 7Edit Fields 6Manual Trigger 4Merge 4Slack 4Other 17

The gates, 9 If and Switch nodes, decide whether anything happens next: a countable part of the system, not a claim about it. The 20 sticky notes carry the reasoning on the canvas, at the point it applies.

The guards, and what each one prevents

HoverTap a card to read what it prevents.

  • The dry run is the default

    Forgetting to switch it off wastes a run; forgetting to switch it on writes thousands of records into a live CRM. Every write sits past that one branch.

  • The dry run shows the payload, not a count

    “1,842 contacts would be created” proves nothing. The exact JSON, and the fields left empty, is where a bad mapping shows.

  • Reserve, create, confirm

    A crash midway leaves a known unknown a person can resolve, instead of an orphan record nothing knows about.

  • Rollback asks for the run by ID

    Not a checkbox: the thing typed is the thing destroyed, so the wrong run cannot be confirmed from muscle memory.

  • Intake normalises exactly like the migration

    Otherwise new leads start recreating the duplicates the migration just spent a week removing.

  • Suppression lives in the same query

    Whether someone has replied is part of the same SQL condition that decides what is due, so no later step can reorder or forget it.

Limits, stated plainly

  • Built, not yet deployed. Every file imports cleanly; there has been no successful run against a live HubSpot portal.
  • The destination is HubSpot. GoHighLevel or Pipedrive means rewriting the migration, the rollback and the create step.
  • Contact-to-deal associations are not created yet. On a real engagement that is a further workflow.
  • Rollback deletes what the migration created. It does not restore records changed in place — and the migration changes none.

Built with

  • n8n
  • HubSpot
  • PostgreSQL
  • Google Sheets
  • Gmail
  • Slack