← Blog
2026-03-10 · 8 min read

From API to CRM: the plumbing that keeps prospect lists clean

Blueprint diagram of a four-node pipeline from API through staging and suppression to the CRM, the final arrow drawn thin to mark the small subset of fields that reach it.

Here is the unglamorous truth about outbound at any real volume: the bottleneck is not copy, targeting, or which sending tool you picked. It is that the list rots, quietly, and nobody notices until reply rates have halved and somebody spends a week trying to work out what changed. Nothing changed. The data got old.

This is about the plumbing between an ecommerce API and a CRM. It is deliberately boring. Boring is the property you want in the layer that everything else depends on, and it is what still functions in month nine when the person who built it has moved on to something more interesting.

The failure mode you are actually preventing

You know the symptoms. Two records for the same company under slightly different domains. A prospect contacted by two people in the same week. A sequence firing at a business that became a customer last month. Bounces climbing on a list that was clean in February. A rep working from an export nobody can date. Six different symptoms, one root cause.

The root cause is that the CRM was treated as the database. A CRM is a system of engagement: it stores what happened between your team and a human being, and it is excellent at that. It is a poor system of record for external data, because external data changes underneath it and CRMs have no native opinion about staleness. Nothing in a CRM ever says this field was true nine months ago and probably is not now.

The architecture in one line: API, then your own staging store, then deduplication and suppression, then the CRM. The CRM only ever receives records that have passed the gauntlet, and it receives a deliberately small subset of fields. Everything else lives upstream where you can query it properly.

Pick a primary key and normalise it exactly once

The key is the domain. Not the company name, which is inconsistent. Not the email, which changes. Not the merchant name, which is a display string. Domains are the only stable canonical identifier in ecommerce data, and every join you will ever do depends on them being handled consistently.

Normalise aggressively and in one place: lowercase, strip protocol, strip the www prefix, strip trailing slashes, strip query strings, reduce to the registrable domain. Decide once whether a subdomain shop counts as the same record as the parent, write the decision down, and never make it again in an ad hoc spreadsheet at eleven at night.

Store the normalised domain as a unique constraint in your staging table and as an indexed custom field on the CRM company object. Every join, every dedupe check, every suppression lookup goes through that one field. This single decision prevents the majority of the mess described in the previous section, and it costs an afternoon.

Pull, stage, diff, upsert — never write straight through

Four steps, in order. Pull from the search endpoint with your filters and write the raw response to storage, unmodified, with a fetched_at timestamp. Stage: parse into typed columns you control. Diff: compare against the previous snapshot for that domain. Upsert: push only the fields that actually changed to the CRM.

Keeping the raw payload is not optional, and people skip it constantly. When a field changes meaning, when your parser has a bug you did not notice for six weeks, or when you want to reconstruct what a segment looked like last quarter, the raw responses are the only path back. Storage costs almost nothing. Re-fetching a hundred thousand records costs time you will not have.

The diff step is where the actual value sits, and it is the step most pipelines omit. It is what converts a static list into an event stream: platform changed, traffic band changed, a Google profile appeared, a contact email appeared, a store went dark. Those events are far better outreach triggers than any static filter, because they are fresh and because nobody else is watching them.

Blueprint diagram of a four-stage pipeline labelled pull, stage, diff and upsert, with a raw archive below and an event stream branching upward from the diff stage.
The upward branch is the only part of the drawing that produces outreach; everything else exists to make that branch trustworthy.

Make the whole job idempotent. Running it twice must produce identical state. If it does not, you will eventually run it twice — after a timeout, during a retry, or because somebody clicked the button again — and the resulting duplicates will take longer to clean than the idempotency would have taken to build.

Suppression lists are the real product

Maintain, at minimum: current customers and every domain associated with them, past customers, open opportunities, explicit do-not-contact requests, hard bounces, competitors, and anyone contacted by anyone on your team in the last N days. That last one is the difference between a professional operation and an embarrassing one.

Apply suppression twice: at push time, when records enter the CRM, and again at send time, immediately before a sequence goes out. Two checks rather than one, because the gap between them is exactly where the awkward sends happen — a prospect signs on Tuesday and receives a cold email on Wednesday from a sequence that was queued last week.

Store suppression by normalised domain and by email address, separately and both. Someone who opts out from one address should suppress their whole company. A company that becomes a customer should suppress every address at that company, including ones you have not seen yet.

Log every suppression hit with a timestamp and a reason. When somebody writes in asking why they were contacted after opting out, the log is the difference between a two-minute reply and a two-day forensic exercise across three tools.

Decide what belongs in the CRM and what emphatically does not

The instinct is to sync everything on the theory that more data is better. Resist it. Roughly a hundred and seventy monthly trend buckets per store is exactly the sort of data that should never enter a CRM: it will not be queried there, it cannot be read there, and it will bloat every export and every page load for the rest of the system’s life.

Push the identifiers — normalised domain, merchant name, country. Push the segmentation labels you genuinely use in views and sequences: platform, traffic band, catalog band, local profile state, score. Push the one or two concrete facts a rep needs to open a conversation. Push the timestamps. That is it.

Keep upstream: the raw fields, the full trend series, the catalog, every historical snapshot, and all derived metrics. Recompute the labels there on a schedule and sync only the labels. Your CRM stays fast and readable, and your analysis stays in a place where you can write a real query.

Blueprint diagram of a dense upstream field, its 170 trend buckets marked, tapering through a neck marked labels only into a sparse CRM field holding domain and score, split by a dashed boundary.
Widening that neck is the most common cause of a CRM nobody can load, and the damage only becomes visible months later.

A clean test for any borderline field: if it cannot appear in a filter, a saved view, or a merge tag, it does not belong in the CRM. Fields that exist only to make the record look thorough are pure cost.

Refresh cadence, drift and death

Stores die. They replatform, rebrand, get acquired, run out of money, and go offline. A list built in January and used in June carries a measurable dead fraction, and every dead record costs you a bounce, which costs you sender reputation, which costs you the campaign — including the parts of it aimed at live prospects who would have replied.

Tier the refresh rather than doing it uniformly. Volatile fields — traffic, trend, detected apps, contact email — monthly across the active segment. Structural fields — platform, productCount, the Google Business block — quarterly across everything. And a liveness check on the specific list immediately before any sequence launches, no exceptions.

Record what you last saw and when you saw it. A field going from present to absent is information, not an error: a contact email disappearing, a Google profile vanishing, a platform flipping from one system to another. Those transitions are the highest-value rows in the entire diff, and a pipeline that only stores current state throws all of them away.

Email hygiene is plumbing, not marketing

Verify before every send, including on data you believe is clean. Addresses go stale silently: mailboxes fill up, people leave, catch-all policies change, domains get parked. Verification is cheap relative to the cost of a bad batch, and the cost of a bad batch is not the bounces themselves but the weeks of degraded delivery that follow.

Segment by verification result rather than making a binary keep-or-drop decision. Catch-all addresses deserve their own lower-volume stream — neither blasted nor discarded — because a meaningful share of them are real people at real companies and you will lose those leads permanently if you treat them as invalid.

Keep per-domain and per-mailbox volume low enough that one bad batch cannot take down your sending infrastructure. Experienced cold-email operators converge on the same conclusion from different directions: fewer, better-targeted sends outperform mass volume. The reason is not politeness. It is that deliverability is a finite shared resource, and every careless send spends some of it.

Ownership, and the weekly audit that catches drift early

Every pipeline needs a name attached to it. Not a team, a person — someone who receives the alert when the nightly job fails, who is allowed to stop a campaign without asking permission, and who is accountable for the numbers below. Shared ownership of data quality reliably produces no ownership of data quality.

Run a fixed audit query every week and read it even when nothing appears to be wrong. Records created this week. Records updated this week. Duplicates by normalised domain, which should be zero. Records with no owner. Records currently in a sequence that also appear in suppression, which should also be zero. Records last refreshed more than ninety days ago. Bounce rate broken down by source segment.

Seven numbers, one screen, five minutes. The point is not the numbers on any given week. It is that drift shows up as a visible trend in something you already look at, rather than as a quarter of wasted sends that you discover afterwards while trying to explain a bad month.

None of this is interesting work, and that is precisely why it is worth doing properly. Copy improves reply rates at the margin. Targeting improves them more. Clean data determines whether the machine functions at all — and the teams that scale outbound without it are simply the ones who have not hit the wall yet.

Put this playbook to work

153,000+ verified ecommerce stores, searchable by your AI agent. Plans from €49/mo.

Get your API key