What Breaks (and Kinda Works) in CRM Systems for Startups
CRM tags don’t scale the way you’d think
At first, every CRM tag feels like a blessing. “Investor-lead-gen-2024-Q2” sounds like a great idea in April. But by July, you’re buried under variants: “Investor-leads-Q2”, “InvestorOutbound2024”, “INV-Q2-test” — and someone on the team added emojis. If you’re not enforcing tag naming programmatically, you’re eventually just filtering by chaos.
We made the mistake of letting marketing freelancers add tags during their outbound. A week later, I was regex-matching fragment slugs just to group contacts. There’s no standard tag audit tool in most CRMs, which means you’ll only realize your taxonomy’s failed when it breaks the filtering pagination. Some systems silently cap dynamic filters to 50 items or less.
The best workaround we landed on was tying tags to campaign IDs already tracked in our outbound tools (like Apollo or Base). Not perfect, but at least that’s consistently structured. Also had to write a morning cron job to grep and re-tag old entries that used legacy structures — lots of fun.
Auto-syncing with Gmail isn’t really syncing
This part annoyed me for months. Just because your CRM says it “syncs” with Gmail doesn’t mean it’s bi-directional or complete. If you archive or snooze something in Gmail? CRM doesn’t know. If you reply from your phone without the tracking pixel or extension on? CRM misses it. And if the CRM API token expires mid-week — you get ghost threads.
We lost context on a founder relationship because his last email reply never pulled into Pipedrive. Support later told me it happens when the response comes from a Gmail alias or delegated mailbox. Makes sense, but it’s not documented anywhere obvious — I had to dig through a forum post from 2019.
“We don’t track Gmail threads sent from aliases due to current API limitations, but we’re working on a workaround.”
They’re not. You’ll need to plug the gaps with something like Zapier, or just train your team to CC the CRM address into Gmail replies manually. Not elegant, but at least you can sleep knowing the thread’s archived somewhere.
Duplicate contacts are not technically duplicates
Fun fact. Two contacts with the same full name and email address can still exist twice in most CRM systems. It usually happens when one is added manually and the other comes in through a form or webhook. One has a phone number, the other has deal history — and neither of them will merge automatically unless you pay for an expensive data hygiene add-on.
HubSpot, for instance, tries to dedupe only on email. But if you’re running a lead enrichment tool that populates from multiple sources, someone’s going to mess it up. One enrichment adds work email. Another appends a personal Gmail. The CRM treats them as two separate humans. They’re not.
Quick deduping triage
- Set up a saved view that filters any contact created within 24 hours of a duplicate email match
- Use an internal tag like “review-dupe-half” on suspicious entries
- Export before merging in bulk — merges can’t be reversed post-execution
- Auto-flag contacts created via webhook and Google Sheets to avoid double creation
- Turn off auto-enrichment on at least one of your connected tools
The day I realized we were tracking MRR across two versions of the same account in Close — with completely different pipelines — I almost deleted the wrong one. Ended up snapshotting both and reassembling in Notion first like some kind of freelancer archeologist.
Reporting is suspiciously optimistic by default
There’s a strange optimism bias built into most CRM reporting dashboards. Take funnel conversion numbers, for example — they often assume stagnants are still “active leads” unless marked dead. So you get fake progress bars that look great… until you realize half the people haven’t replied in two months.
I was scratching my head over our suddenly amazing pipeline health. Then I discovered the default stage aging filter was set to 90 days. We hadn’t moved most people in weeks — the CRM just didn’t care. After reducing the active window to 14 days and excluding paused deals, the win rate dropped by almost 60%. At least it wasn’t lying anymore.
If you do custom dashboarding (using Metabase or similar), make sure your filters don’t just mimic the default CRM logic. You’ll often need to recreate status intents from raw event data instead of trusting the bouncey UI counters.
Webhooks fall apart under soft timeouts
Had a weird edge case last winter — one of our email parsers was slow (was pulling in long attachments from DocuSign). The webhook from the CRM fired nominally, but the call hit a 4000ms lag and silently aborted. No email parsed. No retry. No error reported in the dashboard. We only caught it because a founder asked why their term sheet hadn’t loaded in the doc index.
Turns out the CRM webhook config screen let us set retry logic, but the cloud function layer that picked up the request had a separate GCP timeout threshold. Not aligned. So depending on the latency at that moment, some payloads just died mid-flight. Top-tier duct tape behavior.
The fix was gross: reduced the payload size on CRM by turning off non-essential fields, and updated the cloud function to stream parse instead. I also added a backfill watchdog that pings every 12 hours and looks for dead sequence IDs in the doc queue. Overkill? Yes. But missing term sheets apparently makes people mad.
Lead assignment logic fails silently with country-specific rules
If you’ve ever tried to assign leads based on country rules — especially if you’re dealing with EMEA and APAC — prepare to regularly misroute someone. One CRM we used offered “smart region-based routing” but didn’t treat UK and Great Britain as synonyms. So leads with a .co.uk address went to our EU account exec, but “Great Britain” entries (from Clearbit) went to the US team.
Nobody noticed for five weeks until the timezone reply delays looked awkward enough to ping support. They admitted the lookup was case-sensitive and hardcoded (not regexed), because they were doing a naïve string match, not geographic resolution. That’d be something to mention in the docs, maybe.
As a stopgap, we started normalizing incoming country values using a tiny internal proxy API. It accepts raw data, and translates known quirks: UK → United Kingdom → Europe in our internal schema. The minute a CRM allows dropdown overrides from both manual input and webhook post, you’re gonna get schema drift.
CRM mobile apps lack parity in automation triggers
This one got me unexpectedly. We had a field rep doing follow-ups during a travel week. He marked deals as “qualified” from his iPhone using the CRM app. Great. But none of the automations fired (which were supposed to drop the contact into an email nurturing sequence + Slack alert + assign calendar task).
After an hour of poking around, I realized: the CRM mobile app triggers automation on form SAVE events
, but not on status change events
. On desktop, they’re treated the same. Mobile doesn’t fire the complete change event tree — probably to save battery or bandwidth.
This weirdly isn’t listed in any documentation. But you can see it in the logs if you enable verbose mode in dev tools and inspect webhook callbacks. On mobile, half the state trees just aren’t serialized. We added a secondary cron that scrapes for unprocessed state transitions and retries task creation.
I hate that we have to do this kind of logging coverage. But not as much as I hate when synced contacts ghost automations that worked fine on desktop.
Import/Export history is not preserved as you’d expect
If you’ve ever exported contacts into CSV, edited something, and reimported — hoping to patch just a few fields — you might have been surprised by the fallout. One team member forgot to include the Record ID field during re-import, and the CRM treated it as an entirely new contact. We ended up with several hundred duplicates and some overwritten deal notes.
Bonus: the export tool had a character encoding bug that replaced all smart quotes with junk glyphs. So when we tried to filter by a known comment field (“Interested in 2024 pilot?”), the system failed to match. Even a minor apostrophe difference caused filtering breaks. Unicode sucks sometimes.
“Don’t trust CSV exports unless you inspect at byte level before reimport” — drunk me in Slack, probably
Eventually we wrote a script to sanitize exports and reconcile entries with fuzzy record matching. But it feels ridiculous how fragile the whole cycle is. A CRM should at least warn when reimported data creates N+ duplicates based on known identifiers.