Azure waste

Standard Notification Hubs namespace on a non-production namespace

A Notification Hubs namespace on the Standard tier while tagged non-production.
The agent reports this
Reversible
Why it happens

A Notification Hubs namespace bills a flat monthly unit charge for its tier regardless of how many notifications it actually sends: nothing on Free, 10 US dollars a month on Basic and 200 on Standard. Standard buys a far higher included push allowance, rich telemetry, scheduled and bulk operations and multi-tenancy, none of which a development or test namespace exercises, so the tier is a fixed charge for capacity it will never claim. The saving quoted is the move to Basic rather than to Free, because Free caps pushes and devices far below what even a test estate usually exercises and the difference between the two is 10 dollars against a 190 dollar saving, so naming Free would inflate the figure by 5 per cent for a tier the customer is unlikely to be able to live on. Microsoft documents the tier as a namespace-level setting that applies to every hub inside the namespace, so this is a change rather than a migration, and it can be set back the same way. One thing this cannot check, and says so on every finding rather than implying otherwise: Basic carries lower device and registration limits than Standard, and the agent reads no device counts, so confirm the namespace fits within them before changing the tier.

Paste this into Resource Graph Explorer in the Azure portal.

resources
| where type =~ 'microsoft.notificationhubs/namespaces'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
| extend tier = tostring(coalesce(sku.name, sku.tier))
| where tier =~ 'Standard'
| project name, resourceGroup, location, tier, targetTier = 'Basic', environment = cfopEnv

Reads the TOP-LEVEL sku column, not properties.sku. Microsoft's own ARM template reference shows sku: { name: 'Free' } as a sibling of properties rather than inside it, so a query looking in the properties bag finds nothing and reports a clean estate that is not clean. sku.tier is coalesced in because a namespace returned by an older API version can carry the tier there. The target is Basic and deliberately not Free. Free is a real tier with a real meter at $0, but it caps pushes and devices far below what even a test estate usually exercises, and it would add only $10 to a $190 saving, so naming it would inflate the figure by 5 per cent for a tier the customer probably cannot live on. One precondition neither this query nor the agent can check: Basic carries lower device and registration limits than Standard. Confirm the namespace fits inside them before changing the tier. Microsoft documents the tier as a namespace-level setting that applies to every hub in the namespace, so the change affects all of them at once.

Applies to microsoft.notificationhubs/namespaces
What the agent does about it

A Notification Hubs namespace on the Standard tier while tagged non-production.

It will not fix this one

It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.