74/100 (hygiene pillars) 11 Red12 Amber
| Workflow | Action | Why nobody is reached |
|---|---|---|
| Escalation: alert deal desk | internal email notification | ivo.stern@cadence.example is deactivated |
| Renewal risk: notify renewals team | in-app notification | team Renewals has no active members |
| Workflow | Rotates between | Why nobody is eligible |
|---|---|---|
| Inbound lead rotation (round-robin) | Marketing Ops | no member of Marketing Ops holds a Sales/Service seat |
| Object | Owner field | Records at risk | No owner | Deactivated owner | Workflows |
|---|---|---|---|---|---|
| deals | hubspot_owner_id | 235 | 140 | 95 | Deal stage change: notify owner |
6 workflow conditions test a field that no record carries. Each one resolves the same way on every enrolment, and the column below says which way: a condition that can never match leaves its path unreachable, while one that always matches is a gate standing open. HubSpot reports the empty field under Data Model health; what it does not say is that something is reading it.
| Field | Object | Records with a value | On an empty field | Read by |
|---|---|---|---|---|
| nps_segment | contacts | property no longer exists | never matches | Lifecycle: MQL on form submit |
| form_fill_count | contacts | 0 of 48,200 | never matches | Lifecycle: MQL on form submit |
| onboarding_stage | contacts | 0 of 48,200 | always matches | New deal from qualified lead |
| trial_blocked | contacts | 0 of 48,200 | never matches | Outbound cadence: enroll new SQLs |
| churn_risk_score | deals | 0 of 3,120 | never matches | Renewal risk: notify renewals team |
| escalation_reason | deals | 0 of 3,120 | not stated | Lifecycle: customer on closed-won deal |
1 of these is a checkbox (trial_blocked), which will look populated when you go and check. An unset checkbox displays as “No” throughout HubSpot and in every export, but it holds no value at all, and that is what the condition sees: an empty checkbox matches neither “is false” nor “is true”.
1 condition matches every record rather than none, so the path is not unreachable: every enrolment takes it. That can be the intent, a filter written to exclude nobody yet, so confirm what it is for before changing it.
1 of these names a property that is not on the object at all, so the condition can never match anything.
1 is also written by another workflow, which has either never run or never enrolled a record. That explains the empty column; the branch reading it is stuck either way.
Writers are a floor too: a field no enabled workflow writes can still be filled by an integration, an import, a form or a person. Anything owned by a connected phone system, chat tool or data provider arrives that way. The blast-radius section inventories which non-human actors already write to this portal.
Read from workflow enrolment criteria and branch conditions only. Emails, lists, calculated properties and reports also read fields and are not visible here, so treat this as a floor. A further 1 branch condition reads a field on an associated record (contact_country). Reading a field from a record the workflow does not enrol is ordinary HubSpot; the workflow names the association but not the object behind it, so these were not checked either way and are not findings.
| Workflow | Last edited |
|---|---|
| Webinar follow-up 2024 (paused) | 2024-05-21 |
| Copy of trade-show import cleanup | 2024-09-03 |
Governance hygiene over the portal's users. Severity: Good Watch Act
| Signal | Count | % | Severity |
|---|---|---|---|
| Super Admins | 2 | 14.3% | Good |
| Super Admins inactive in window | 0 | Good | |
| Deactivated users still Super Admin | 1 | Watch |
| Seat | Activity total | Status | |
|---|---|---|---|
| dane.koch@cadence.example | sales-enterprise | 1,275 | active |
| lena.roos@cadence.example | sales-enterprise | 0 | deactivated |
| rosa.lindt@cadence.example | sales-enterprise | 1,275 | active |
Per-object hygiene signals (read-only counts). Severity: Good Watch Act
| Signal | Count | % | Severity |
|---|---|---|---|
| No owner | 9,100 | 18.9% | Watch |
| Owned by deactivated users | 1,240 | 2.6% | Act |
| Stale (>180d) | 23,800 | 49.4% | Act |
| Missing email | 7,400 | 15.4% | Act |
| Missing lifecyclestage | 7,400 | 15.4% | Act |
| Custom properties | 82 | Watch |
66 of 82 custom properties on contacts carry no description. Nothing is wrong with the data; the portal's own vocabulary is simply unexplained, which is what makes a field nobody recognises survive for years. This counts the property description the editor shows; the data-entry instructions box beside it is not exposed by the API, and nothing here can be written back.
| Signal | Count | % | Severity |
|---|---|---|---|
| No owner | 820 | 12.8% | Watch |
| Owned by deactivated users | 160 | 2.5% | Act |
| Stale (>180d) | 2,900 | 45.3% | Act |
| Missing name | 1,100 | 17.2% | Act |
| Missing domain | 1,100 | 17.2% | Act |
| Custom properties | 44 | Good |
35 of 44 custom properties on companies carry no description. Nothing is wrong with the data; the portal's own vocabulary is simply unexplained, which is what makes a field nobody recognises survive for years. This counts the property description the editor shows; the data-entry instructions box beside it is not exposed by the API, and nothing here can be written back.
| Signal | Count | % | Severity |
|---|---|---|---|
| No owner | 140 | 4.5% | Good |
| Owned by deactivated users | 95 | 3.0% | Act |
| Stale (>180d) | 540 | 17.3% | Watch |
| Missing amount | 280 | 9.0% | Watch |
| Missing closedate | 280 | 9.0% | Watch |
| Open, past close date (of 819 open with a close date) | 402 | 49.1% | Act |
| Open, 90+ days in the same stage (of 880 open) | 173 | 19.7% | Watch |
| Custom properties | 41 | Good |
At least 402 of the 819 open deals that carry a close date are past it. A further 61 carry no close date at all, so they cannot be counted either way and the figure is a floor rather than a total.
This counts how long a deal has sat in its current stage, and nothing else: a deal here may well have been worked recently. Activity dates are too sparsely filled across portals to qualify it without understating.
33 of 41 custom properties on deals carry no description. Nothing is wrong with the data; the portal's own vocabulary is simply unexplained, which is what makes a field nobody recognises survive for years. This counts the property description the editor shows; the data-entry instructions box beside it is not exposed by the API, and nothing here can be written back.
| Signal | Count | % | Severity |
|---|---|---|---|
| No owner | 1,240 | 62.6% | Act |
| Owned by deactivated users | 22 | 1.1% | Watch |
| Stale (>180d) | 410 | 20.7% | Watch |
| Missing subject | 130 | 6.6% | Watch |
| Missing hs_pipeline_stage | 130 | 6.6% | Watch |
| Custom properties | 18 | Good |
The no-owner count above is at least partly by design: 1 enabled workflow blanks the owner on tickets (Ticket queue: release owner overnight). Records cleared this way are counted here, because they genuinely have no owner and every owner-addressed notification and rotation on them still fails. Confirm the intent before reading this row as neglect.
14 of 18 custom properties on tickets carry no description. Nothing is wrong with the data; the portal's own vocabulary is simply unexplained, which is what makes a field nobody recognises survive for years. This counts the property description the editor shows; the data-entry instructions box beside it is not exposed by the API, and nothing here can be written back.
Duplicate detection is left to HubSpot's native Data Quality tools (Breeze); this section complements them with portable, scored signals that need no Data Hub.
7 properties are already written by enabled workflows. Before an AI agent is pointed at one of these fields, know that a workflow already sets it on a trigger — an agent and a workflow writing the same property is a collision to resolve first. Read-only: this names the overlap, it does not recommend changing any workflow.
| Property | Workflows writing it | Records enrolled (90d) | Enabled workflows |
|---|---|---|---|
form_fill_count | 1 | 4,180 | Form fill: stamp date and count |
form_fill_date | 1 | 4,180 | Form fill: stamp date and count |
phone | 1 | 4,180 | Form fill: stamp date and count |
hubspot_owner_id | 2 | 2,392 | Inbound lead rotation (round-robin) · Ticket queue: release owner overnight |
lifecyclestage | 2 | 1,336 | Lifecycle: MQL on form submit · Lifecycle: customer on closed-won deal |
dealstage | 1 | 118 | New deal from qualified lead |
pipeline | 1 | 118 | New deal from qualified lead |
Records enrolled counts records entering those workflows in the window, not property writes: one enrolment may write the field once, several times or not at all. Read it as how hard the automation around a field is running, which is what separates a field many workflows could write from one they actually do.
At least one workflow writes
lifecyclestage on an associated record, not on the object the workflow enrols. The collision to
resolve is on that associated object.
3 automated actors already write to this portal without a human in the loop. An AI agent joins these, not an empty portal — know what already acts before adding another. Read-only: this inventories what writes, it does not judge or change any integration.
| Actor | Object | Records written |
|---|---|---|
| Workflow automation | contacts | 2,380 |
| Browser extension / connected app | contacts | 812 |
| Workflow automation | deals | 190 |
| Integration | contacts | 145 |
| Integration | deals | 36 |
| Seat | Monthly | Annual |
|---|---|---|
| core | EUR 75 | EUR 900 |
| developer | EUR 0 | EUR 0 |
| partner | EUR 0 | EUR 0 |
| revenue-hub | EUR 140 | EUR 1,680 |
| sales-hub | EUR 150 | EUR 1,800 |
| service-hub | EUR 150 | EUR 1,800 |
| view-only | EUR 0 | EUR 0 |
| Seat | Current | Recommended |
|---|---|---|
| sales-hub | 7 | 4 |
| service-hub | 2 | 2 |
| core | 5 | 5 |
| view-only | 0 | 3 |
4 potential seat change(s) (4 downgrade, 0 upgrade); 10 unchanged.
Marketable contacts that never engage still consume the marketing-contacts tier. Read-only counts; engagement = marketing-email opens or clicks (last 180d).
Marketing-contacts add-on cost (source: HubSpot list (Enterprise marketing contacts, estimate)): current EUR 800/month; after trimming the idle slice EUR 400/month.
Potential saving: EUR 4,800/year (EUR 400/month) if the idle slice is trimmed below the next tier boundary — a renewal-negotiation starting point, not a quote.
Which Enterprise and Data Hub features this portal demonstrably uses, from data the scan already reads. Two limits frame it. Portal-wide Enterprise features come with any Enterprise hub, so dropping the last Enterprise hub loses them all, not only that hub's own. And the most common reasons to hold each product leave no trace in a public API. So this table says what was found, never that a tier is unneeded.
| Feature | Requires | Status | Evidence |
|---|---|---|---|
| Custom objects | Enterprise, any hub | not found | no custom schema |
| Sequence enrolment in workflows | Sales or Service Hub Enterprise | found | 1 fixed sender(s), 1 contact-owner workflow(s) |
| Custom code | Data Hub Professional | not found | no enabled workflow |
| Format data | Data Hub Professional | found | 1 workflow(s): Form fill: stamp date and count |
| Send a webhook | Data Hub Professional | not found | no enabled workflow |
| Dataset enrolment | Data Hub Professional | not found | no enabled workflow |
| Scheduled enrolment | Data Hub Professional | not found | no enabled workflow |
Not measurable here, so verify before changing tier. Marketing Hub Enterprise: multi-touch revenue attribution, customer journey analytics, adaptive testing, lookalike lists, email approvals, predictive lead scoring, partitioned assets, hierarchical teams (the teams endpoint lists members, not parents), custom behavioral events and business units (scopes not requested). Data Hub: data sync, datasets used anywhere but workflow enrolment, dedupe and the Data Quality Command Center, Snowflake data share.
Currently on a paid seat that exceeds measured activity. Downgrade frees the seat cost without affecting their work.
| Current | Recommended | Confidence | Rationale | |
|---|---|---|---|---|
| emma.voss@cadence.example | sales-enterprise | core ↓ | high | 12 contacts created; 12 lead(s) owned; 12 quote(s) built |
| leon.fischer@cadence.example | sales-enterprise | view-only ↓ | low | no measured activity in window. Reading zero here is not evidence of idleness: this portal counts tickets by who owns them, and an enabled workflow blanks that owner on a schedule ('Ticket queue: release owner overnight'), so the count shows only what survived the last run |
| tom.adler@cadence.example | sales-enterprise | view-only ↓ | low | no measured activity in window. Reading zero here is not evidence of idleness: this portal counts tickets by who owns them, and an enabled workflow blanks that owner on a schedule ('Ticket queue: release owner overnight'), so the count shows only what survived the last run |
| bram.ott@cadence.example | core | view-only ↓ | low | no measured activity in window. Reading zero here is not evidence of idleness: this portal counts tickets by who owns them, and an enabled workflow blanks that owner on a schedule ('Ticket queue: release owner overnight'), so the count shows only what survived the last run |
Owned by deactivated or deleted users. They remain visible to active Sales Hub users but have no live owner. Transfer to a current user or archive.
| Owner userId | Sequences | Sample names |
|---|---|---|
| 213 | 3 | Outbound: demo no-show follow-up; Trial day-3 nudge; Re-engagement: cold leads |
| Current | Recommended | Confidence | Rationale | |
|---|---|---|---|---|
| leon.fischer@cadence.example | sales-enterprise | view-only ↓ | low | no measured activity in window. Reading zero here is not evidence of idleness: this portal counts tickets by who owns them, and an enabled workflow blanks that owner on a schedule ('Ticket queue: release owner overnight'), so the count shows only what survived the last run |
| tom.adler@cadence.example | sales-enterprise | view-only ↓ | low | no measured activity in window. Reading zero here is not evidence of idleness: this portal counts tickets by who owns them, and an enabled workflow blanks that owner on a schedule ('Ticket queue: release owner overnight'), so the count shows only what survived the last run |
| emma.voss@cadence.example | sales-enterprise | core ↓ | high | 12 contacts created; 12 lead(s) owned; 12 quote(s) built |
| dane.koch@cadence.example | sales-enterprise | sales-hub | high | Super Admin -- review configuration activity manually |
| rosa.lindt@cadence.example | sales-enterprise | sales-hub | high | Super Admin -- review configuration activity manually |
| jules.maas@cadence.example | sales-enterprise | sales-hub | high | 140 HubSpot-dialed call(s) in window; 3 forecast submission(s) in window |
| paul.frei@cadence.example | sales-enterprise | sales-hub | high | owns 1 sequence(s); 140 HubSpot-dialed call(s) in window; 3 forecast submission(s) in window |
| rens.kuiper@cadence.example | service-enterprise | service-hub | low | Service Hub work cannot be measured on this portal: 'Ticket queue: release owner overnight' blanks ticket ownership on a schedule, and owned tickets are the only Service signal the API exposes |
| ada.wolff@cadence.example | service-enterprise | service-hub | high | 264 ticket(s) owned and touched in window -- measured Service Hub work |
| bram.ott@cadence.example | core | view-only ↓ | low | no measured activity in window. Reading zero here is not evidence of idleness: this portal counts tickets by who owns them, and an enabled workflow blanks that owner on a schedule ('Ticket queue: release owner overnight'), so the count shows only what survived the last run |
| finn.devries@cadence.example | core | core | high | 12 contacts created; 12 lead(s) owned; 12 quote(s) built |
| mia.dahl@cadence.example | core | core | high | 110 contacts created; 25 task(s) touched; 18 note(s) authored; 110 lead(s) owned; 110 quote(s) built |
| nora.berg@cadence.example | core | core | high | 110 contacts created; 25 task(s) touched; 18 note(s) authored; 110 lead(s) owned; 110 quote(s) built |
| sara.holm@cadence.example | core | core | high | 110 contacts created; 25 task(s) touched; 18 note(s) authored; 110 lead(s) owned; 110 quote(s) built |
| Seat | Contacts | Sequences | Meeting links | RR/team links | Calls | Custom objs | Deals | Tasks | Notes | Forecasts | Leads | Quotes | Tickets | Logged in | Total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| leon.fischer@cadence.example | sales-enterprise | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | no | 0 |
| tom.adler@cadence.example | sales-enterprise | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | no | 0 |
| emma.voss@cadence.example | sales-enterprise | 12 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 2 | 0 | 12 | 12 | 0 | no | 41 |
| dane.koch@cadence.example | sales-enterprise | 320 | 0 | 0 | 0 | 140 | 0 | 22 | 90 | 60 | 3 | 320 | 320 | 0 | no | 1275 |
| rosa.lindt@cadence.example | sales-enterprise | 320 | 0 | 0 | 0 | 140 | 0 | 22 | 90 | 60 | 3 | 320 | 320 | 0 | no | 1275 |
| jules.maas@cadence.example | sales-enterprise | 320 | 0 | 0 | 0 | 140 | 0 | 22 | 90 | 60 | 3 | 320 | 320 | 0 | no | 1275 |
| paul.frei@cadence.example | sales-enterprise | 320 | 1 | 0 | 0 | 140 | 0 | 22 | 90 | 60 | 3 | 320 | 320 | 0 | no | 1276 |
| rens.kuiper@cadence.example | service-enterprise | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | no | 0 |
| ada.wolff@cadence.example | service-enterprise | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 2 | 0 | 0 | 0 | 264 | no | 270 |
| bram.ott@cadence.example | core | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | no | 0 |
| finn.devries@cadence.example | core | 12 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 2 | 0 | 12 | 12 | 0 | no | 41 |
| mia.dahl@cadence.example | core | 110 | 0 | 0 | 0 | 0 | 0 | 4 | 25 | 18 | 0 | 110 | 110 | 0 | no | 377 |
| nora.berg@cadence.example | core | 110 | 0 | 0 | 0 | 0 | 0 | 4 | 25 | 18 | 0 | 110 | 110 | 0 | no | 377 |
| sara.holm@cadence.example | core | 110 | 0 | 0 | 0 | 0 | 0 | 4 | 25 | 18 | 0 | 110 | 110 | 0 | no | 377 |
HubSpot matches a subscription type's translation on the **exact** language code an email declares, with no fallback from `nl-nl` to `nl`. One language declared under two codes therefore splits the audience across two translation sets, and a code with no translation defined renders its subscription name, preference centre and unsubscribe text in the portal default instead.
nl is declared under 2 codes
— 6 emails, 31,700 sends| Code | Emails | Sends |
|---|---|---|
nl | 3 | 25,200 |
nl-nl | 3 | 6,500 |
1 emails declare no language at all (1,200 sends) — they fall back to the portal default whatever the copy is written in.
“Klantnieuwsbrief” is sent in nl-nl but
translated into nl — 3 emails, 6,500
sends. Same language, different code, and HubSpot matches the code exactly: those sends fall back
to the subscription's default text and the translation is never used.
Fix by standardising on one code per language, or by defining the subscription-type translation for every code in use.
1 of these email uses smart content (1,800 sends), varying SUBJECT. HubSpot names the smart fields but exposes neither their rules nor their variations, so HubScan cannot read what they swap in. Worth knowing: a smart rule can serve a different language, while the preference centre and unsubscribe text still follow the email's single declared language code — so those readers get one language in the body and another in the footer. Check those emails by hand.