Back to blog
🛠 v0.5.0 May 9, 2026

v0.5.0 — Zabbix alerts, canned responses, CRM rebuild

Webhook-driven alert sources with a Zabbix preset, server-rendered canned responses, a three-kind CRM with locations + domain auto-join, a system health dashboard, and a left-rail admin layout.

v0.5.0 widens the surface area: monitoring tools talk to Resolvd directly, the customer/vendor/internal split that exists in real orgs now exists in the schema, and the admin area gets the layout it should have had from day one.

Zabbix alert sources

POST /api/webhooks/zabbix/<token> is the new front door for alert ingestion. Add a source under Admin → Integrations → Alert sources, copy the generated token (shown once), paste the bundled Zabbix media-type script into your monitoring side, and problems show up as Resolvd tickets. Recoveries close them — auto-resolve toggle controlled per source.

Severity → priority is a per-source JSONB map with sensible defaults and a “Reset to defaults” button. The dedup table (external_alert_event, UNIQUE on source + event_id + type) means Zabbix retries can’t duplicate-create. Tickets gain external_ref (e.g. zabbix:1041), external_source, and external_alert_source_id, plus two togglable list columns: Vendor ref and Alert ref — both with the NATO phonetic readback popover on hover.

If the Zabbix host carries INVENTORY.POC.PRIMARY.EMAIL, that email auto-attributes both submitted_by and assigned_to and adds the user as a follower. Unmatched emails leave an audit row so you can wire the contact later.

A Backfill button on the source detail page hits Zabbix’s problem.getevent.gethost.get (with selectInventory: poc_1_email), normalizes everything to synthetic webhook payloads, and runs them through the same ingest pipeline. Bearer auth is tried first, with a fallback to legacy auth for older Zabbix versions.

How to use: Admin → Integrations → Alert sources → New source → pick zabbix. Copy the token and the media-type snippet, paste both into Zabbix, save. Hit Backfill once if you want existing open problems to import.

Admin → Alert sources detail for a Zabbix source — token banner, severity map editor, recent events table, copy-able media-type script.

Canned responses with tag substitution

canned_responses (scope = global or user, optional category, body, project_ids[], use_count) backs a 📋 popover next to the ticket comment composer. Search filters live; results group by category; the picker only surfaces responses scoped to the current project (or globally unscoped).

Tag substitution runs server-side via services/cannedRender.js. Supported: {ticket.ref}, {ticket.title}, {ticket.priority}, {ticket.url}, {ticket.vendor_ref}, {submitter.firstName/name/email} (with {ticket.submitter} as alias), {assignee.*}, {actor.*}, {site.name}, {site.url}. Unknown tags pass through unchanged so a {not.a.tag} literal in the body never trips the renderer.

Insert increments use_count so the popover floats your most-used templates to the top within their category. Admins/Managers manage globals; everyone can author personal-only entries.

How to use: Admin → Workflow → Canned responses to manage. On any ticket, click 📋 next to the comment composer to insert.

Ticket detail with the canned-response picker open — categorized list, search box, "Insert" preview pane showing rendered tags.

CRM rebuild — vendors, customers, internal

companies.kind is now vendor | customer | internal and project_id is nullable. Internal companies model your own org (or a customer’s org if you’re an MSP) and carry company_members linking users with optional location + role label. Customer companies connect to projects via the new company_projects join. Vendors keep the old project-scoped semantics.

A new locations table holds name, code, address, timezone, phone, use_extensions, is_primary, and a soft-archive flag. Contacts get location_id + extension; the contact-create UX pre-fills the phone number from the location when use_extensions=true.

companies.auto_add_domains TEXT[] on internal kind feeds services/companyAutoJoin.autoJoinInternalCompanies(userId), which fires on SSO first-login and invite-acceptance. Saving an updated domain list retroactively syncs existing matching active users — useful when you add a new acquired domain after onboarding.

Branding gets three feature toggles (Vendor, Customer, Internal) — Vendor + Internal default ON, Customer (MSP) default OFF — so single-tenant installs don’t see customer UI they don’t need.

How to use: Admin → Companies. Pick a kind on the create form; the detail tabs adjust (Vendor: Contacts/Locations/Notifications · Customer: Contacts/Locations/Projects · Internal: Members/Locations).

Admin → Companies in master-detail layout — left list filtered to "Internal" kind, right pane showing Members + Locations tabs with auto-add domains chip list.

System health page

GET /api/system-health returns scheduler heartbeats, DB stats (size, uptime), ticket counters by status and priority, inbound queue counts, alert-source last-seen + event count, and email-backend statuses. The page auto-refreshes every 30 seconds and shows a per-job health dot — ok, stale (heartbeat older than 2× cadence), error, or never_ran.

The auto_close job was missing its heartbeat write — added in this release and registered in the job ledger alongside muted_digest and inbox_subscription_renewal.

How to use: Admin → Site → System health.

Admin → System health — scheduler job grid with status dots, DB stats card, ticket counters, inbound queue panel, alert-source last-seen list.

Left-rail admin + PageShell + column visibility

Layout sweep. <PageShell variant="wide|standard|narrow"> is the new width primitive — lists/dashboards opt into wide, ticket/project detail into standard, settings forms into narrow. The global max-w-7xl is gone from Layout.jsx so each page picks its own.

Admin is now a left-rail nav (sticky on desktop, hamburger drawer on mobile that auto-closes on route change), grouped into People · Workflow · Integrations · Site · Data. Manager sees a subset. Admin Email backends, Inbound, Companies, and Alert sources are master-detail — list left, selected-item right pane — replacing the nested-collapsible pattern that ran out of headroom around five children.

Column visibility ships as <ColumnPicker> + the useColumnPrefs(tableKey) hook, backed by users.preferences.hidden_columns. TicketList is the first consumer (Ref + Title always-on, the rest togglable). Same hook will roll out to the rest of the table-shaped pages over the next couple of releases.

How to use: Admin sidebar groups everything by area; on TicketList, click the column picker (top-right of the table) to hide what you don’t need.

Admin layout — sticky left-rail nav grouped People/Workflow/Integrations/Site/Data, master-detail pane on the right showing Email backends with one account selected.

Full changelog

v0.5.0 on GitHub has the complete commit list. This post covers the headline features; smaller wins (closing-salutation signature trim, saved-views project_id capture, dependency bumps) live in the changelog.