A marketing database that can prove it's allowed to be used.
A consent-capture, storage, and suppression system for a fictional B2B SaaS company — cookie consent gated with Google Consent Mode v2, layered email consent captured in HubSpot as five distinct signals instead of one flag, and suppression logic verified against a live, shared HubSpot portal rather than a clean sandbox. Built entirely on free-tier tools.
The problem
Lumen Metrics, a small e-commerce analytics SaaS company, had spent eight months growing its contact database with no consent discipline: every demo request, ebook download, and imported conference lead was emailed the same way, with no record of who had actually agreed to marketing contact — and no cookie consent on the website at all, with analytics tracking firing unconditionally on every page load. As the company's first Marketing Operations hire, the brief was to answer one question with evidence, not assumptions: can this database actually be used legally and safely for marketing?
What I set out to build
- Cookie/tracking consent that genuinely gates analytics — not a banner that displays while tracking fires anyway.
- Marketing email consent modeled as its own explicit, unbundled, non-required choice — never inferred from someone simply submitting a form.
- A HubSpot data model that separates tracking consent, marketing consent, legal basis, and subscription status, rather than one generic flag.
- Suppression logic that actively controls who's eligible for a send, not just a dashboard that describes who should be.
- A system I could prove was working, not just assert — evidenced with real screenshots, not staged ones.
How the pieces fit together
The system runs as two deliberately separate layers, meeting only at the moment a real send happens.
Track A — Platform-enforced
HubSpot's native subscription status and legal-basis property. This layer is unconditional — no custom logic built on top of it can override it, which is exactly why it's the layer this project's suppression segments were checked against.
Track B — Custom-built
Six purpose-built properties and four rule-based segments, engineered specifically to represent what HubSpot's free tier has no native way to model — category-level marketing consent chief among them.
Consent is modeled as five distinct signals rather than one flag:
Can this anonymous browser be tracked — held in localStorage, signaled to GTM's dataLayer.
Did this known contact agree to Product Updates, Promotions, or Sales Outreach — specifically, not generically.
Why we may process this person's data at all, which can differ from why we may email them.
HubSpot's native, unconditional enforcement layer — the layer that actually blocks a send.
Has this person asked to see, correct, or delete their data — captured, not assumed.
Stack
What's actually running
Cookie consent banner
Hand-built in vanilla JavaScript rather than a packaged CMP. Stores the visitor's choice in localStorage, re-announces it to the dataLayer on every page load, and stays reopenable anytime via a persistent "Cookie Settings" control in the footer.
Google Tag Manager & Consent Mode v2
A Consent Initialization tag sets Google Consent Mode v2 defaults to denied before anything else runs. A custom trigger, reading the banner's own consent event through a Data Layer Variable, is the only trigger available to the GA4 tag — meaning GA4 is structurally incapable of firing before consent, not merely delayed.
HubSpot CRM data model
Six custom properties in a dedicated Consent & Privacy group, modeling tracking consent, category-level marketing consent, and suppression reason as separate fields — deliberately not collapsed into one. Getting there required first auditing and archiving four unused properties from an earlier portfolio project, after hitting HubSpot Free's 10-property, portal-wide cap.
HubSpot Forms
Three forms — Demo Request, Newsletter & Ebook, Manage Preferences — each unbundling "submit this form" from "agree to marketing" as two separate, optional actions.
Preference management & real unsubscribe enforcement
A native HubSpot GDPR consent element on the Manage Preferences form ties directly to real subscription status, layered on top of the custom audit property rather than replacing it. Verified with an actual send-and-unsubscribe test, not a simulated one — HubSpot's own reporting captured the unsubscribe reason natively.
Suppression segments
One segment, Suppressed - Consent Not Valid, built as the single source of truth; two "Valid Marketing Contacts" segments reference its membership rather than re-implementing the same rules — a redesign made mid-build once HubSpot's actual segment-membership filter made the cleaner pattern possible.
Proving it works, not just building it
QA happened in two layers: five test contacts engineered to sit right on the boundary of the rules rather than safely inside them, and three genuine free-tier platform constraints hit and resolved mid-build — all tested against a live, shared HubSpot portal already carrying contacts and properties from an earlier project, not a clean sandbox.
An honest read on that 93.75%
That figure looks alarming out of context, and it's worth explaining rather than hiding: this portal is shared with an earlier portfolio project, so most of that 15 is sample contacts and legacy leads that simply never had Lumen Metrics' consent properties set at all — which correctly matches the "Marketing Consent Categories is unknown" condition. It's the rule working exactly as designed on data it was never scoped to, which is itself a real lesson: a segment built to run in a production portal would need an additional filter scoping it to this project's own contacts, and I've noted that in the limitations below rather than quietly cropping the screenshot to hide it.
- 01Portal-wide property cap — HubSpot Free's 10-custom-property limit blocked the sixth property mid-build; traced to four unused properties from an earlier project and archived them to free a slot. Fixed, verified.
- 02Mid-build product rename — HubSpot renamed "Lists" to "Segments" across the entire product; every segment-related step had to be re-verified against the live interface rather than trusted from documentation written days earlier.
- 03Silent archive failure — attempting to archive an old property failed with no clear cause at first, blocked by an existing asset still referencing it; resolved by locating and clearing that reference before archiving.
- 04Segment scope check — reviewing the Valid Marketing Contacts segment surfaced one contact needing manual exclusion via HubSpot's individual-contact exclusion control, alongside the rule-based logic — a reminder that automated segmentation still benefits from a human spot-check before a real send.
What's actually verifiable
Measured: the "Valid Marketing Contacts - Product Updates & Tips" segment, filtered by category consent and membership exclusion from the Suppressed segment, resolved to exactly the two contacts predicted at design time — out of a shared portal containing sixteen. Consent Mode v2 was confirmed live: zero analytics activity before consent, tracking beginning within seconds of it being granted.
Full configuration, all documentation, and the complete test log are in the case study and documentation in the project repo.
What I'd tell a hiring manager directly
Challenges
The most instructive snags weren't compliance questions at all — they were free-tier platform mechanics: a property cap that had nothing to do with this project specifically, and a mid-build product rename that made previously-correct instructions wrong within the same week. Working around both, rather than upgrading past them, is most of what this project actually demonstrates.
Limitations
- The suppression segment is currently portal-wide rather than scoped to this project's own contacts, which is correct behavior but would need an added filter before running in a shared production portal.
- Custom Subscription Types and Workflow-based automation both require Marketing Hub Starter or above; this project substitutes a custom audit property and a documented manual review process instead.
- Two consent-context properties (tracking and marketing consent capture dates) are populated manually per contact rather than automatically, since reliable auto-population without a paid Workflow proved fragile enough not to trust.
- This is an educational portfolio project, not a certified compliance system, and does not make any real company legally GDPR or CCPA compliant.
What I'd build next
- Add a project-scoping filter to every segment before this pattern is reused against a shared or production portal.
- Upgrade to Marketing Hub Starter to replace the manual audit process with real Workflow-based automation.
- Turn the five test scenarios into a repeatable regression checklist, re-run after any property or segment change.
- Route any real deployment through actual legal review before pointing this at a real company's database.