Story

The Afternoon I Deleted 200 Live CRM Contacts

By Felix Maru · October 7, 2026 · 6 min read

It was a Tuesday afternoon and I was in cleanup mode. The company CRM had been accumulating junk for months: duplicate records from a bad CSV import, stale leads with no deal activity, incomplete entries with no email address attached. They were cluttering the pipeline view and making segmentation unreliable. My job that afternoon was to purge the noise.

I had done this kind of thing before. Write a filter, verify the criteria against a few sample records, check the count looks reasonable, run the delete. Standard database housekeeping. I was adapting a script I had used on similar cleanup jobs, pointing it at this CRM's API, and the logic I wanted was clear in my head: contacts with no associated deals and no logged activity in the last 18 months. That combination would catch only the genuinely dead records.

I previewed the filter output. It returned 203 records. I scanned the first page. Incomplete names, missing phone numbers, blank company fields. Everything looked like the stale junk I was trying to remove. I was satisfied. I ran the delete.

What the Activity Log Showed

The CRM dashboard refreshes in real time. I expected the contact count to drop from roughly 2,400 to somewhere around 2,200. It dropped to 2,197.

That felt right. I opened the activity log to confirm what had been removed.

The timestamps were all within the last sixty seconds. Two hundred and three deletions, one after another, stacked in a column. I scrolled through the names and stopped when I hit one I recognized: a warm lead from earlier that week, someone the sales lead had flagged as a priority follow-up for Friday.

My stomach went cold. I kept scrolling. More recent names. People who had filled out forms in the last three weeks. A prospect from a campaign that had gone out just four days ago.

The Diagnosis

I went back to the filter logic in the script. The condition was using or where I had intended and. Contacts with no deals or no activity in 18 months. Not both. Either one qualified a record for deletion.

The second condition alone, no activity in 18 months, had a manageable scope. But the first condition, no associated deal, caught every new lead in the pipeline who was still early enough that no deal record had been created yet. Those are exactly the leads a sales team is actively working.

It was a single-character error in a Python dictionary key. I had typed "operator": "or" thinking I was writing a placeholder comment and would change it before running. I had not changed it. The API parsed it faithfully and did exactly what I told it to do.

Trying to Rebuild

I checked for a recycle bin. Some CRMs have one for records deleted through the interface. This CRM, on the plan the company was using, applied a recycle bin only to UI-level deletions. Records deleted through the API were removed immediately. No 30-day window. No undo endpoint. The documentation confirmed this in a small note I had not noticed before.

I had two things to work with: a weekly automated export that had run four days earlier, and the email history in the shared sales inbox where some of these contacts had written in directly.

I spent the next ninety minutes rebuilding. The four-day-old export gave me names, emails, and phone numbers for most of the deleted records. Email threads in the inbox filled in the context for the most recent entries, the ones who had come in since the last export. We lost around 15 records cleanly, people who had submitted forms in the last four days with no email trail to cross-reference. The rest came back, imperfect but recoverable.

The sales lead was more understanding than I deserved. He said it was the kind of thing you do once and never again.

He was right about the second part.

The Gap the "Are You Sure?" Dialog Doesn't Cover

The thing about bulk operations through an API is that they have no UI safety net. The confirmation dialog, the warning about permanent deletion, the highlighted count of records about to be affected: those exist in the browser interface because someone built them in. They are not features of the underlying data model. They are features of the application layer that sits on top of it.

When you script against an API directly, you skip that layer. You are talking to the data itself, with nothing in between. The API will do whatever your code tells it to do, at speed, without hesitation. That is its job. The job of not making catastrophic requests is yours.

The "are you sure?" dialog is not a safety mechanism. It is a reminder that safety is your responsibility. At the API level, there is no reminder.

Three Rules I Have Not Broken Since

Before any bulk delete, export the full target set to CSV first. Not a sample of it. The full set. Open it, scroll through it, confirm it contains only records you intend to remove. This costs about sixty seconds. It has saved me from two separate mistakes in the two years since that afternoon.

Run a test delete on three records before the full operation. Not to confirm that the filter is "probably right," but to confirm what it actually matches when run against real data. You can read a filter condition and believe you understand it. You can run it against three specific records and see exactly what it touches. Those are different things. The test run is where errors surface at a cost of three records instead of two hundred.

Check the platform's documentation on API delete behavior before writing the script. Some CRMs route API deletes through the same recycle bin as UI deletes. Some do not. Some have different behavior depending on the record type or the plan tier. This takes ten minutes to verify once and is not worth skipping.

The irony is that after that afternoon, the CRM was in better shape than it had been in months. The stale records were gone and the rebuilt records were more accurate than the originals because we had to verify each one. But ninety minutes of rebuilding is ninety minutes of not doing actual work, and the sales lead spent an hour checking his pipeline for gaps. The real cost was not the records. It was the time and the trust.

If you work with CRMs, databases, or any tool that offers a bulk operation feature: export first, test on three records, then run. In that order, every time, without exception.

If you want to talk through safer patterns for bulk operations against live data, drop me a line.

Share 𝕏 in ✉

Comments