Breeze RMM 0.87.0 — UniFi network integration, downloadable + branded reports, M365 email-to-ticket, distributor catalog import, and the BE-16 vulnerability correlation job — plus invoice/contract polish and a batch of correctness + hardening fixes.
A feature-heavy release (286 files) that's still a light upgrade for operators: 10 idempotent migrations (all new-table creates + cheap metadata-only column changes — no large-table rewrites or backfills), no new required environment variables, and no breaking changes.
Summary
- UniFi network integration — connect a partner's UniFi Site Manager cloud account to map UniFi sites → Breeze sites and reconcile UniFi devices as discovered assets, with an optional agent-side read-only deep-telemetry collector. Opt-in per partner; off until you connect an account (#2040).
- Reports — persisted, downloadable report runs and a new client-facing Security & Compliance Posture report with a partner-branded PDF (logo, scorecard, per-device tables) for insurance/vetting (#2058).
- Email-to-ticket (M365) — connect a Microsoft 365 shared mailbox so customer email becomes tickets and replies send back from that mailbox, with no MX/forwarding changes (#2045). Inbound alias is now editable independently of the partner slug (#2060).
- Billing / distributors — import hardware & subscriptions from TD SYNNEX and Pax8 into the catalog, then quote/contract them with internal unit cost, markup, SKU/part-number, and margin tracking (kept off customer-facing documents); plus a QuickBooks customer import that creates Organizations + default Sites (#2025, #2048, #2044).
- Vulnerability management — the BE-16 correlation step is now wired into a daily job, gated per-org behind a new
vulnerabilityconfig-policy feature (default OFF) (#2050). - Fixes & hardening — self-hosted email-to-ticket / Stripe webhooks reaching their handlers again, MSI bootstrap enrollment, one-click alert suppress, MCP partner-role resolution, and a UniFi GDPR-cascade gap (#2054, #2049, #2020, #2019, #2052/#2055).
Added
- UniFi network integration — connect a partner's UniFi Site Manager cloud account (encrypted API key), map UniFi sites to Breeze sites, and reconcile UniFi devices into discovered assets. An optional agent-side, read-only deep-telemetry collector polls the local UniFi Network controller for richer device/client data. A BullMQ sync worker runs on a 30-minute scheduler. Opt-in: nothing happens until a partner connects an account. (#2040)
- Downloadable report runs + Security & Compliance Posture report — report runs are now persisted and downloadable, and there's a new client-facing Security & Compliance Posture report (for insurance/vetting) rendered as a partner-branded PDF (logo, scorecard, per-device tables). CSV/Excel exports retain full per-row data; the PDF is the polished client deliverable. (#2058)
- M365 shared-mailbox email-to-ticket — connect a Microsoft 365 shared mailbox (admin-consent flow) so customer email becomes tickets and replies are sent back from that mailbox via Microsoft Graph — no MX or forwarding changes. On first connect only new mail is ticketed (no historical backfill). Optional; see upgrade notes for the Azure app + env vars. (#2045)
- QuickBooks customer import — browse, select, and import QuickBooks customers as Breeze Organizations, each with a default Site carrying contact/address. MFA-gated and idempotent on re-import. (#2048)
- Distributor catalog import + line cost & margin — import hardware and subscriptions from TD SYNNEX and Pax8 into the catalog, then quote and contract them with internal unit cost, markup, SKU/part-number, and margin tracking. Cost and SKU are snapshotted on the line and kept off customer-facing documents. (#2025)
Improved
- Editable inbound email alias — partner admins can set a custom inbound email local-part, decoupled from the partner slug, so an awkward auto-generated
tickets@…address can be changed without DB access. Ships dark (defaults to the slug); the original slug address keeps working forever. (#2060) - Editable organization type — Org Type (Customer vs Internal) is now editable on Settings → Organization → General, not just at create time. (#2061)
- Vulnerability correlation daily job — the previously-uncalled BE-16 correlation step now runs daily (13:00 UTC), gated behind a new
vulnerabilityconfig-policy feature ({ enabled }, default OFF). Enable Vulnerability Scanning on a policy and assign it to an org to populate findings. (#2050) - Invoice polish + internal margin panel — payments are gated to issued (non-draft) invoices, the document header dedupes when no seller is named, operator hints surface (taxable-but-no-tax-rate, no billing contact), and a new internal "Margin" summary panel appears on the invoice editor/detail rails (gated on
invoices:read, never shown on customer documents). (#2044) - Contracts list/detail UX — the bulk bar keeps selected rows visible, "Delete drafts" is only offered when a draft is selected, empty states split into first-run vs filtered-empty, the redundant Organization column is dropped in org-locked embeds, and date inputs render correctly in dark mode. (#2024)
- Device list & sidebar quality-of-life — the device list Columns dropdown gains a "Reset to defaults" button (restores column visibility + order), and the sidebar gains a "Collapse all" button that collapses every section except the one holding the active page. (#2015)
Fixed
- Self-hosted email-to-ticket & Stripe webhooks reaching their handlers — a wildcard
authMiddlewareon the webhook-CRUD router blanketed the entire/webhooks/*subtree, 401'ing the public signature-gated inbound-email, Stripe, and Stripe Connect webhooks before their HMAC handlers ran (broke self-hosted email-to-ticket, #2053). Auth is now applied per-route. (#2054) - MSI bootstrap CA enrollment token — MSI installs succeeded but agents never enrolled ("no bootstrap token present") because a deferred EXE custom action couldn't read
[CustomActionData]. Fixed by referencing[OriginalDatabase]directly. Takes effect in this release's signed MSI; the immediate workaround remains a property-based silent install (SERVER_URL=… ENROLLMENT_KEY=…). (#2049) - One-click alert suppress — single-alert Suppress sent no request body, so the API's required
{ until }always returned 400 and suppress never worked. Adds a duration picker (default 24h) and surfaces the server's specific error. (#2020) - Confirm contract cancel — the terminal Cancel action on a contract (irreversible; stops all future invoicing) previously fired on a single unconfirmed click and now routes through a confirmation dialog. Pause/resume stay immediate. (#2024)
- MCP owning-partner resolution + SSE public URL — org-scoped (manual) API keys reached MCP
tools/callwith no partner, causing "Insufficient permissions: no role assigned" for partner-admin users; the key's owning partner is now resolved for role resolution only (RLS visibility unchanged). The SSEendpointevent also now advertises the configured public base URL instead of a barehttp://URL behind an HTTPS proxy. (#2019) - UniFi org data now included in GDPR erasure — the five new
org_idUniFi tables weren't registered in the org-deletion cascade, which would have orphaned UniFi data on org delete; both are now enrolled inORG_CASCADE_DELETE_ORDER. (#2052, #2055)
Security
- Per-route auth on
/webhooks(defense-in-depth) — replacing the/webhooks/*wildcardauthMiddlewarewith per-route auth ensures the public, HMAC-verified webhook endpoints (inbound email, Stripe, Stripe Connect) are reached and validated by their own signature checks instead of being blanket-401'd. Requires a new build to take effect. (#2054)
Self-Hosting / Upgrade Notes
No breaking changes. Existing orgs are unaffected by the new feature gates below — UniFi, M365 email-to-ticket, and vulnerability scanning are all opt-in and stay off until you turn them on.
- Upgrade command. Bump
BREEZE_VERSION=0.87.0in.env, thendocker compose pull api web && docker compose up -d. If you build from source, runpnpm installafter pulling. Node pin is unchanged (v22.20.0). - Database — 10 idempotent migrations, auto-applied on boot via
autoMigrate(unlessAUTO_MIGRATE=false). They are all new-table creates (UniFi ×2, ticket mailbox connections, report runs, report type) with RLS enabled inline, plus cheap metadata-only column changes (quote_lines/invoice_linesdescriptionDROP NOT NULL, new accounting/cost/SKU/inbound-local-part columns) and indexes on the new empty tables. No large-table rewrites or backfills — boot is fast. - No new required environment variables. The M365 email-to-ticket feature adds three optional vars —
TICKET_MAILBOX_M365_CLIENT_ID,TICKET_MAILBOX_M365_CLIENT_SECRET(API) andPUBLIC_TICKET_MAILBOX_APP_ID(web). Unset = the feature is disabled and nothing breaks at boot. To enable it, register a separate multi-tenant Azure app with Graph application permissionsMail.ReadWrite+Mail.Send, redirect URI<PUBLIC_URL>/api/v1/tickets/mailbox/callback, set those three vars, and map them in theapi/webenvironment:blocks of your compose (a value in.envalone isn't enough). If you reverse-proxy over HTTPS, make surePUBLIC_API_URLis set to your external URL so the MCP SSE endpoint advertises the right scheme (#2019). - Behavior changes & feature flags.
vulnerabilityconfig-policy feature — default OFF. A new per-org policy gate; the daily BE-16 correlation job (13:00 UTC) only populates findings for orgs whose assigned policy enables scanning. No existing org is opted in. (#2050)- UniFi integration — opt-in. No sync runs until a partner connects a UniFi Site Manager account; the agent telemetry collector is off until enabled per site. (#2040)
- Inbound email alias ships dark —
partners.inbound_local_partdefaults to null (= use the partner slug), so existing inbound addresses are unchanged. (#2060)
Full Changelog: v0.86.0...v0.87.0
What's Changed
- feat(web): reset-columns button on device list + collapse-all in sidebar by @ToddHebebrand in #2015
- Confirm contract cancel + polish contracts list/detail UX by @ToddHebebrand in #2024
- Catalog distributor import + quote/contract line cost & margin by @ToddHebebrand in #2025
- Polish invoices + internal margin summary panel by @ToddHebebrand in #2044
- feat(unifi): UniFi network integration — cloud Site Manager (P1) + agent deep telemetry (P2a) by @ToddHebebrand in #2040
- test(web): de-flake PamRuleModal preview zod-error test by @ToddHebebrand in #2047
- Import QuickBooks customers as Orgs + Sites by @ToddHebebrand in #2048
- fix(installer): MSI bootstrap CA never delivered the enrollment token by @ToddHebebrand in #2049
- feat(vuln): wire BE-16 correlation into a daily job, gated per-org by config policy by @ToddHebebrand in #2050
- fix(tenancy): register UniFi org-scoped tables in the GDPR cascade by @ToddHebebrand in #2052
- fix(api): per-route auth on /webhooks so inbound-email + Stripe webhooks reach their handlers by @ToddHebebrand in #2054
- fix(unifi): enroll UniFi org-scoped tables in ORG_CASCADE_DELETE_ORDER (#2051) by @ToddHebebrand in #2055
- M365 Exchange shared-mailbox email-to-ticket (inbound + outbound) by @ToddHebebrand in #2045
- docs: reliability macOS/Linux de-junk note for 0.86.0 by @ToddHebebrand in #2016
- fix(mcp): resolve owning partner for org-scoped keys + honor public base URL in SSE endpoint by @ToddHebebrand in #2019
- fix(web): make one-click alert Suppress work (send a duration) by @ToddHebebrand in #2020
- chore(deps): bump node from
fb71d01toa0b9bf0in /docker by @dependabot[bot] in #2043 - chore(deps): bump node from
fb71d01toa0b9bf0in /apps/api by @dependabot[bot] in #2042 - chore(deps): bump node from
fb71d01toa0b9bf0in /apps/web by @dependabot[bot] in #2041 - chore(deps): bump bullmq from 5.79.1 to 5.79.2 by @dependabot[bot] in #2037
- chore(deps): bump eslint from 10.5.0 to 10.6.0 in the linting group by @dependabot[bot] in #2036
- chore(deps): bump hono from 4.12.26 to 4.12.27 in the hono group by @dependabot[bot] in #2032
- chore(deps): bump the tailwind group with 2 updates by @dependabot[bot] in #2030
- chore(deps): bump @tanstack/react-query from 5.101.0 to 5.101.2 in the tanstack group by @dependabot[bot] in #2029
- chore(deps): bump @types/office-js from 1.0.592 to 1.0.593 in the typescript-tooling group by @dependabot[bot] in #2027
- chore(deps): bump @playwright/test from 1.61.0 to 1.61.1 in /e2e-tests by @dependabot[bot] in #2035
- chore(deps): bump playwright from 1.61.0 to 1.61.1 in /e2e-tests by @dependabot[bot] in #2034
- chore(deps): bump @anthropic-ai/sdk from 0.105.0 to 0.106.0 in /e2e-tests by @dependabot[bot] in #2033
- chore(deps): bump the react-native-animation group with 2 updates by @dependabot[bot] in #2026
- chore(deps): bump @astrojs/starlight from 0.40.0 to 0.41.1 in the astro group by @dependabot[bot] in #2028
- fix(deps): repair duplicate-key corruption in pnpm-lock.yaml by @ToddHebebrand in #2056
- chore(deps): bump @aws-sdk/client-s3 from 3.1065.0 to 3.1076.0 by @dependabot[bot] in #2038
- chore(deps): bump @types/node from 26.0.0 to 26.0.1 in /e2e-tests by @dependabot[bot] in #2031
- feat(reports): downloadable report runs + security posture report + impeccable branded PDF redesign by @ToddHebebrand in #2058
- fix(ci): allowlist placeholder test email domains in customer-PII guard by @ToddHebebrand in #2059
- feat(tickets): editable inbound email alias (decouple from partner slug) by @ToddHebebrand in #2060
- feat(web): make organization type editable on the General settings tab by @ToddHebebrand in #2061
- fix(ci): allowlist tickets.localhost + theirdomain.com in customer-PII guard by @ToddHebebrand in #2062
Full Changelog: v0.86.0...v0.87.0