← Back to projects
GTM Engineering Automation & Integrations

HubSpot CRM Cleanup: From a cluttered CRM to a lifecycle system that maintains itself

I audited a cluttered HubSpot portal, cleaned out a third of the database, and built a lifecycle and lead status system that keeps contacts moving (or retires them) without anyone babysitting it.

Before and after: contacts 30,963 to 19,574, hard bounces 6.5% to 0.8%, and a lifecycle that times out stale leads

Final Outcome Overview

The CRM went from 30,963 contacts to 19,574, the hard bounce rate dropped from 6.5% to 0.8%, and email deliverability moved up to an A grade. Every contact now has a clear lifecycle stage and lead status, stale leads time out on their own, and a weekly digest catches regressions before they pile up.

Pain Point to Solve

The HubSpot portal at the B2B HealthTech company I work for had grown for years without rules. Thousands of contacts had no email or bounced, unsubscribed contacts still counted toward the marketing contact bill, duplicate companies split account history, and over a thousand deals still belonged to people who had left. Lead status meant little: contacts sat "in progress" forever, so sales could not tell who was actually being worked, and reporting on the funnel was guesswork.

My Role

I owned it end to end: the audit, the prioritised plan, the scripts, the HubSpot workflows, the documentation for sales and marketing, and the monitoring that runs afterwards. I worked with sales leadership to sign off anything destructive.

How I Approached It

Audit first. I built a set of Claude Code audit skills and Python scripts against the HubSpot API that score the portal across contacts, companies, deals, engagement, ownership and deliverability. That gave me a baseline instead of opinions.

Prioritise. The audit fed a phased implementation plan ordered by impact and dependencies: protect sender reputation and billing first, then fix ownership and duplicates, then build the lifecycle logic.

Automate the boring parts. Most fixes ran as scripts with dry runs and backups: deleting 697 contacts with no email, merging 353 duplicate companies, archiving 1,001 deals owned by former employees (recoverable, after sales leadership confirmed they were noise), assigning an owner to 70 orphaned contacts, and closing 2,297 contacts that had been stuck in progress with no activity. Suppressing bounced and unsubscribed contacts cut marketing contacts by about a third.

Prevent regressions. Every one off fix got a matching workflow or check, so the same mess cannot quietly come back.

The Lifecycle System

New contacts enter as Leads and are promoted to MQL automatically when they come in through a form. Sales takes them to In Progress and owns the step to SQL. Deals created and won move contacts to Opportunity and Customer.

The core is a two tier timeout. If nobody engages with an In Progress contact for 30 working days, it moves to Attempted. After another 30 working days of silence it becomes Closed, drops back to Lead and is set to non-marketing. A logged call, meeting, reply or email click at any point brings it straight back to active. Opportunities and Customers are never touched, and contacts in a nurture campaign are protected from the timeout. About 4,900 contacts went through this system on its first pass.

Around it sit demotion workflows: an MQL or SQL marked Unqualified goes back to Lead.

Keeping It Clean

A weekly digest runs unattended as a scheduled cloud routine. It reruns the audit, compares it against the previous week using 29 threshold rules, writes a report and opens HubSpot tasks for anything that slipped. Tasks are deduplicated, so a recurring problem updates one task instead of flooding the queue. A lifecycle dashboard and a plain language flowchart keep sales and marketing on the same rules.

Results

  • Contacts: 30,963 to 19,574
  • Hard bounce rate: 6.5% to 0.8%, deliverability graded A
  • Marketing contacts: down by about a third
  • 353 companies merged, 1,001 dead deals archived, 70 contacts given an owner
  • 2,297 stale contacts closed, about 4,900 more handled by the timeout
  • A self-running weekly check instead of a yearly cleanup

Tech Stack

HubSpot
Claude Code