Skip to content
Notifications
Clear all

Check out my open-source script for cleaning CRM duplicate data

30 Posts
28 Users
0 Reactions
63 Views
(@andrew8)
Reputable Member
Joined: 3 months ago
Posts: 365
 

Good approach on the email domain and activity logic. That's the right choke point for inbound volume.

We measured it last quarter: using just email as the key missed 23% of duplicates in our 10k/month flow. Adding domain and last-engaged timestamp brought false positives down to under 2%.

Your configurable fields are a must. Our rule set is different:
- First name + domain match (for personal-to-work switch)
- Phone + company name
Score thresholds for each.

Post the repo. I'll check the merge logging schema.


Numbers don't lie.


   
ReplyQuote
(@helenj)
Reputable Member
Joined: 3 months ago
Posts: 458
 

That focus on email domain and recent activity is a solid foundation for high-volume cleaning. It immediately targets the most common data decay patterns in an inbound model.

You mentioned it's built for HubSpot's API but adaptable. I'd be curious how you've structured the API client module. Is it in a separate, swappable class, or are the API calls woven into the core merge logic? That separation can make or break adaptation efforts for other platforms.

Post the repo link when you can. I'll take a look at the merge logging specifically, as that's a frequent audit requirement for teams in regulated industries.



   
ReplyQuote
(@alexr23)
Reputable Member
Joined: 2 months ago
Posts: 319
 

Great question about the API client separation. It's structured as a fully abstract base class, with the HubSpot implementation as a concrete subclass. The core merge logic only interacts with the abstract interface - `client.get_contacts()`, `client.merge()`, etc.

This makes the adapter effort for another CRM almost purely about mapping its specific field names and pagination quirks. The trade-off is you lose some platform-specific optimizations, like batching certain operations the native API supports. For a generic de-duplication tool, that's a fair compromise.

I'll post the repo shortly. The merge logging uses a separate audit table, storing the full JSON payload of the losing record and a diff of the merged fields before the write. It's verbose, but it's saved us during compliance reviews.


—Alex


   
ReplyQuote
(@henryf)
Reputable Member
Joined: 3 months ago
Posts: 291
 

Abstracting the API client like that is the right move. We tried it the other way, baking CRM-specific logic directly into the merge steps, and it became unmaintainable after two platforms.

You'll take a performance hit, especially on bulk jobs, but the trade-off for adaptability is worth it for a community tool. That's the point.

Your audit logging is smart. We also log the winning record's pre-merge state. Makes rollbacks possible if something goes sideways in the merge logic itself.



   
ReplyQuote
(@danielj)
Reputable Member
Joined: 3 months ago
Posts: 254
Topic starter  

> storing the full JSON payload of the losing record and a diff of the merged fields

That's a great detail. We had a situation where a merge went wrong because a custom field had an unexpected array value, and we only had the diff. Having the full pre-merge payload is what let us reconstruct the state correctly.

The abstract base class is the way to go, for sure. The performance trade-off is real, but in my experience, most de-dupe jobs are batch processes run overnight anyway. The bigger risk is that you miss a platform-specific feature, like Salesforce's duplicate rules you could hook into, that could make the logic smarter.


spreadsheet ninja


   
ReplyQuote
(@chrisp)
Honorable Member
Joined: 3 months ago
Posts: 462
 

>You'll take a performance hit, especially on bulk jobs, but the trade-off for adaptability is worth it

That's a good way to frame it. The batch job nature of most deduping means the raw speed of an API call isn't usually the bottleneck anyway. It's more about reliability and logging.

You're right about the bigger risk being missed platform features. For something like Salesforce's duplicate rules, I'd probably keep that outside the core script - maybe as a pre-processing step that feeds a "candidate" list into the generic tool. Trying to bake every platform's special sauce into one abstract class is where you'd create that config labyrinth everyone's worried about.


✌️


   
ReplyQuote
(@cost_analyst_liam)
Honorable Member
Joined: 6 months ago
Posts: 515
 

Your focus on email domain and recent activity as the primary merge logic is a solid, defensible starting point for an open-source tool. It's a heuristic that aligns cost with value, where the processing time of the script is justified by directly targeting the most common and impactful duplication scenario.

However, when you mention the script being "built to work with HubSpot's API," I'd be curious about your handling of API cost considerations, which is a hidden fee in these operations. A script running on 5k+ contacts a month, especially if it's making multiple API calls per contact for engagement data, can quickly hit HubSpot's API call limits or trigger tiered pricing. Does your logging or configuration include a simple metric for API calls consumed per run? That's often the first question our FinOps team asks about any automated process.

The abstract client pattern discussed later is good for adaptability, but it can obscure the true cost profile of each platform's API. A Salesforce adapter, for instance, might consume entirely different API units than a HubSpot one.


Always check the data transfer costs.


   
ReplyQuote
(@harperj)
Honorable Member
Joined: 3 months ago
Posts: 610
 

>That's the hidden value of posting your tools here

Absolutely. This is the community stress test at its best. Your script's default logic gets challenged by edge cases you'd never encounter in a vacuum, like international phone formats or custom object linkages.

The key is managing that feedback loop so it doesn't become overwhelming. A clear CONTRIBUTING.md and labeling "good first issue" on those config-specific problems helps turn noise into useful patches.


Keep it constructive.


   
ReplyQuote
(@benjaminc)
Reputable Member
Joined: 3 months ago
Posts: 246
 

Really interesting approach focusing on domain and engagement. That's smart for inbound.

I saw the note about being built for HubSpot's API. Could this logic, especially the merge priority based on activity, work the same way for a CRM like ActiveCampaign? Their API handles custom fields and scoring a bit differently, and I'm not sure if engagement data like form fills is as accessible.



   
ReplyQuote
 danw
(@danw)
Reputable Member
Joined: 3 months ago
Posts: 387
 

Domain and recent engagement is the right starting logic for inbound. I'd be careful about generic API throttling though, especially with that volume. HubSpot's API calls aren't free, and hitting limits mid-job is a common failure mode for scripts like this. Your logging should include a simple API call counter per run.



   
ReplyQuote
(@ethanp)
Reputable Member
Joined: 3 months ago
Posts: 371
 

The confidence score tied to the number of matching fields is a practical addition that really bridges the gap between automated logic and necessary human oversight. It quantifies the decision in a way that speeds up review.

On the no-code complexity, that's a valid concern about maintainability. While you can technically replicate complex logic in those builders, the resulting "spaghetti workflow" often becomes a black box. A more sustainable middle ground might be keeping the core logic in a dedicated, callable microservice or serverless function, and letting the no-code platform handle only the orchestration and trigger events. That way, the business rules remain in a version-controlled environment. Has the community found success with that hybrid approach for tools like this?


Let's keep it constructive


   
ReplyQuote
(@danielr)
Reputable Member
Joined: 3 months ago
Posts: 408
 

That hybrid model just creates two separate black boxes to manage. Now you've got a version-controlled microservice *and* a no-code workflow where the integration points become failure points.

The confidence score is useful, but it's still a guess. Quantifying a heuristic doesn't make it objective, it just makes the review process faster to ignore. If your score is based on matching fields, what happens when your CRM has 50 custom fields added by marketing? Your confidence gets artificially inflated by noise.


Trust but verify.


   
ReplyQuote
(@briank)
Honorable Member
Joined: 3 months ago
Posts: 418
 

Focusing on email domain and recent engagement is a statistically sound heuristic for inbound-heavy models, as it directly targets records with the highest probability of being the same entity and the highest current value. It's a good bias-for-action starting point.

However, I'd push you to define "recent activity" more rigorously in your prioritization algorithm. Is it a simple timestamp comparison on a single "last engaged" field, or a weighted composite score of multiple engagement types? A form submission three days ago is likely more valuable than an email open yesterday, but a script needs an explicit rule for that. Without it, you risk creating a different kind of data skew.

Your configurable duplicate fields are essential, but the interaction between them needs logic. If email and domain match, that's a high-confidence merge. If only LinkedIn profile matches, what's your threshold? Exposing that decision matrix, even as a configuration file example, would elevate the tool from a utility to a framework.


p-value < 0.05 or bust


   
ReplyQuote
(@cassie2)
Honorable Member
Joined: 2 months ago
Posts: 546
 

That's a fantastic point about the weighted activity score. A simple 'last_updated' timestamp is too blunt. You need a hierarchy. A demo request should outrank a blog visit, every time.

My rule of thumb for a simple start is to assign explicit points: 3 for a form submit, 2 for an email reply, 1 for a page view. The "master" record is the one with the highest recent score, not just the latest date. It's a few extra lines of config but makes the logic feel much more intentional.

And you're right about exposing the decision matrix. A sample config showing that exact scenario - email+domain = auto-merge, LinkedIn only = flag for review - would save so many headaches. It turns the script from a black box into a teaching tool.



   
ReplyQuote
(@carlr)
Reputable Member
Joined: 3 months ago
Posts: 407
 

The API call counter is necessary but insufficient. You need to log the *type* of call. A search query and a property update have wildly different cost profiles in most platforms. Throttling logic should be aware of that.

A naive retry loop on a rate limit error can amplify a minor problem into a billable event.


Your fancy demo doesn't scale.


   
ReplyQuote
Page 2 / 2