Skip to content
Solutions Blueprint

Syncing Salesforce Campaign Members Past the Row Ceiling

Hero featured image

A Salesforce object holding more than 3 million campaign membership records sat on one side of this migration, and HubSpot's then-standard 1.5 million row custom object ceiling sat on the other. We built the sync architecture and the inclusion-list gate governing when it goes live, and worked through the capacity decision that let the full historical set through without a cutoff date.

Executive Summary

context-header-icon

Context

A global enterprise SaaS company was moving its marketing operations off a twelve-year-old Marketo instance and onto HubSpot, with Salesforce staying in place as the system of record for campaign membership across North America, EMEA and APAC. Its CampaignMember table had grown past 3 million records, more than double HubSpot's standard custom object tier.

what-we-built-header-icon

What We Built

We built a field-level Salesforce Contact and Campaign Member sync on HubSpot's native connector, gated by an active list that excludes internal, spam and test records before the toggle moves to production.

tech-stack-header-icon

Tech Stack

  • HubSpot Marketing Hub Enterprise, Salesforce (SFDC), LeanData, Clearbit, Snowflake, FiveTran

Not a fit if your Salesforce campaign membership history sits comfortably under a HubSpot custom object's row ceiling, since this capacity decision matters only once that ceiling is in reach. It also assumes a live Salesforce CRM, and depends on your team maintaining the inclusion list gating production sync.

the-challenge-header-icon

The Challenge

Under the legacy platform, every contact and campaign record crossed into Salesforce through a single, marketer-controlled "Sync to Salesforce" flow step, so nothing reached the CRM until someone sent it. HubSpot's native connector replaced that checkpoint with continuous, field-level sync, exposing a capacity problem the old architecture never faced: Salesforce's CampaignMember object already held more than 3 million rows, roughly 3.2 million in production, against HubSpot's then-standard 1.5 million row ceiling for a custom object.

our-approach-header-icon

Our Approach

Activating the connector against the full Campaign Member history hit the row ceiling immediately, and no field mapping fixes a capacity problem. Filtering the sync to a subset of campaigns or a cutoff date would have kept the row count under the ceiling but discarded attribution history the business needed intact.

Instead, the resolution was a capacity purchase, cleared before Phase 2 sync configuration began, so the full historical set could sync with no exclusion. The sync keys on email, collapsing duplicate Salesforce Lead and Contact records for one address into one HubSpot Contact, and it runs continuously once an active list, "SFDC Sync: Included Contacts", excludes internal, spam and test records and the toggle moves to production. Campaign Member association to Contact proved connector-native, so a planned custom lookup workflow was never built.

impact-header-icon

Impact

check-icon

Full campaign membership history syncs without a cutoff date

The row ceiling that would have forced a filtered sync got resolved with additional storage instead, so the complete production Campaign Member set, more than 3 million records, moved across with no campaigns dropped. Every prior campaign touch stays available for the attribution work built on top of it.

check-icon

Duplicate Salesforce records collapse into one HubSpot Contact

The sync keys on email, so a Salesforce Lead and Contact sharing an address resolve to a single HubSpot Contact rather than two competing records. That removes a duplicate-record class the marketing team would otherwise reconcile by hand on every list pull.

check-icon

Campaign Member to Contact associations need no custom workflow

HubSpot's native association sync ties a Campaign Member record to its Contact automatically, simpler than planned. A custom lookup workflow had been scoped for that step; it was never built, and the maintenance burden it would have carried never existed.

check-icon

Production activation runs through a documented inclusion gate

An active list, "SFDC Sync: Included Contacts", excludes internal domains, spam and test records before the sync toggle moves to production, so activation is a reviewable gate rather than a judgement call made once and forgotten.

Technical Blueprint
1

Any field change on an included contact moves to HubSpot continuously once the sync toggle is on, with no batching interval to wait out. The match key is email, so HubSpot's own Contact dedupe absorbs duplicate Salesforce Lead and Contact rows for one address into one record.

2

Salesforce's CampaignMember object held more than 3 million records, roughly 3.2 million in production, against HubSpot's then-standard 1.5 million row ceiling. Rather than filter by status or campaign type, the resolution was purchasing additional row capacity, so the full historical set syncs with no cutoff date.

3

HubSpot's association sync settings link a Campaign Member record to its Contact without a custom lookup step, originally scoped as its own build. The connector carries that relationship natively, so the workflow it would have needed was removed from the plan entirely.

4

The sandbox carries its own cap, 200,000 contacts and roughly 201,000 Campaign Member records, distinct from the production row question above. That ceiling blocked a later proof of concept's live registration test, a build-environment limit rather than the production capacity question it resembles.

Diagram of Salesforce CampaignMember records syncing to HubSpot Contacts, gated by an inclusion list and a resolved row-capacity ceiling.

Source Gate & capacity HubSpot Sandbox Salesforce Lead/Contact source records SFDC Campaign Member 3M+ records Inclusion list gate excludes internal/spam/test Custom object capacity purchased, not filtered HubSpot Contact deduped on email HubSpot Campaign Member custom object, 1.5M ceiling HubSpot sandbox 200k contact ceiling Live registration test Campaign Architecture PoC field change trigger mechanism 1 email dedupe sync mechanism 1 row ceiling hit mechanism 2 capacity purchased, syncs mechanism 2 sandbox ceiling blocks test mechanism 3

FAQ

What happens when a Salesforce object outgrows HubSpot's custom object row limit?

You have two options: filter the sync so fewer rows cross over, discarding history outside the filter, or purchase additional custom object capacity so the full set syncs intact. Here the CampaignMember object held more than 3 million records against a 1.5 million row ceiling, and the capacity purchase kept attribution history intact.

Does HubSpot's native Salesforce connector handle Campaign Member to Contact associations automatically?

Yes, once the connector's association sync settings are configured, a Campaign Member record ties to its Contact without a custom workflow. Confirm this early: a lookup step is easy to scope defensively before you know the native behaviour covers it, and here it did.

footerCTA footerCTA-mobile
Spice up your inbox
Sign up for our newsletter
Don't worry - we only average, like, two emojis per subject line.