Skip to main content

Changelog

Release notes and version history for Escalated and its framework packages.

v0.9.0Escalated

v0.9.0 — Newsletter system admin UI

Newsletter system admin UI (Wave 0)

The shared Vue/Inertia frontend now ships the full newsletter admin surface, consumed identically by every Escalated backend.

Added

  • Newsletter admin UI — compose / index / show pages, deliveries table, analytics tiles, dynamic segment-filter builder, list-member table, markdown editor, merge-field dropdown, preview iframe (+ Storybook stories). (#75)
  • usePermissions() composable + permission-gated admin nav — the newsletter entry renders only for users with newsletters.manage, matching backend enforcement. (#96)
  • Central translations consumed from @escalated-dev/locale; local src/locales/*.json win as overrides.

Changed

  • Newsletter admin pages wired to locale strings (i18n). (#96)

Full Changelog: https://github.com/escalated-dev/escalated/compare/v0.8.0...v0.9.0

View on GitHub
v1.5.1Escalated for Laravel

Patch release on top of v1.5.0 (the newsletter system release).

Fixed

  • CI/build: ignore the laravel/framework 11.x security advisories that Composer 2.9+ excludes during dependency resolution, which was breaking the Laravel 11 compatibility test matrix. Scoped to this package's root config.audit.ignore — does not propagate to host apps. No runtime/code changes.

See v1.5.0 for the newsletter system, add_follower, ticket subjects, and permission enforcement.

View on GitHub
v1.5.0Escalated for Laravel

v1.5.0 — Newsletter system

Added

  • Newsletter system — admin-only broadcast feature: campaigns, recipient lists (static + dynamic segments), templates, per-recipient deliveries with open/click tracking, one-click unsubscribe, view-in-browser, and ESP bounce/complaint webhooks (Postmark/Mailgun/SES/SendGrid). Driven by the escalated:newsletters:dispatch scheduled command (per-minute rate limiting, retry backoff, auto-pause on high bounce rates). Disabled by default behind escalated.enable_newsletters. (#103, #128)
  • add_follower workflow action — auto-subscribe a host user as a ticket follower from a workflow rule. (#127)
  • Ticket subjects — attach host-app entities a ticket is about (Project, Customer, asset…), distinct from the requester. (#89)
  • escalated.permissions is now shared with the Inertia frontend for per-permission nav gating. (#129)

Security

  • Newsletter admin routes now enforce newsletters.manage (all actions) and newsletters.send (send/test). Previously seeded but unchecked. (#129)

Full changelog: https://github.com/escalated-dev/escalated-laravel/blob/main/CHANGELOG.md

View on GitHub
v1.4.1Escalated for Laravel

Patch release fixing upgrade-safety regressions found in a breaking-change audit of v1.4.0.

Fixed

  • Skill management on upgraded installs. v1.4.0 added the routing_tag_ids/routing_department_ids skill columns by editing the original create_escalated_skills_table migration, which never re-runs on an existing install — so apps upgraded from an earlier version were missing the columns and every skill create/edit failed (Skill::saving() always writes them). Adds a Schema::hasColumn-guarded backfill migration (no-op on fresh installs).
  • AssignTicketRequest (Admin/Agent assign endpoints) again validates that agent_id exists, so unknown/garbage input returns a clean 422 instead of a 500. UUID/string keys are still accepted.
  • AssignmentService::__construct() $skillRoutingService is now optional (container fallback), restoring the v1.3.0 new AssignmentService($manager) single-argument signature.

Upgrade notes (from < 1.4.0)

  • The int → int|string widening for UUID/string user-key support touches the public TicketDriver::assignTicket() contract and Ticket methods (assign, follow, unfollow, isFollowedBy, scopeAssignedTo). If you implement TicketDriver or subclass Ticket with int-typed parameters, widen them to int|string to avoid a PHP "must be compatible" fatal. Callers are unaffected.
  • TicketAssigned::$agentId is now int|string (a string for UUID/string-keyed apps). Integer-keyed apps are unchanged.

Full changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.4.0...v1.4.1

View on GitHub
v1.4.0Escalated for Laravel

Highlights

UUID / string host user keys — Escalated for Laravel now works with host apps whose user primary key is a UUID, ULID, or string, not just an integer. SavedView::scopeForUser() and every other user-id parameter accept int|string (fixing a TypeError 500 on /support/admin/tickets for non-integer-keyed apps), user ids are never cast to int (which corrupted UUIDs), and the package's user-referencing migration columns are auto-typed to match your user_model via the new escalated.user_key_type config (auto by default). Integer-keyed apps are unaffected.

Custom Ticket Actions — register host-defined agent ticket buttons that dispatch a TicketCustomActionTriggered event (with an audit note) when clicked.

Added

  • Custom Ticket Actions via events (#107, #108)
  • Auto-detect host user key type for migration columns + escalated.user_key_type (#112)
  • Skills-based ticket routing (#95)
  • Mobile customer & guest support API (#104)
  • Expanded SSO provider configuration (#96)
  • Shared escalated-dev/locale translation consumption

Fixed

  • UUID/string host user keys throughout (#110)
  • Restrict skill assignment to role-bearing users; transactional skill store/update (#100)
  • Show 2FA recovery codes after confirmation (#97)

Security

  • Bump transitive qs 6.15.1 → 6.15.2 in the demo host-app (CVE-2026-8723) (#113)

Full changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.3.0...v1.4.0

View on GitHub
v0.8.0Escalated

v0.8.0 - Admin users page + staff-panel banner on /support

Added

  • Admin → Users page (#62): a paged, searchable list of host users at /support/admin/users with toggles to grant or revoke is_admin / is_agent. Lands a Users entry in the admin nav between Roles and Capacity. Self-demote on the Admin toggle is disabled in the UI and rejected server-side. Pairs with escalated-laravel#94. Surfaced from #60, where the reporter asked "how do you create agent?"
  • Staff-panel banner on /support (#61): the customer index scopes its ticket list to the current user, so admins / agents who landed on /support saw only their own tickets and concluded that customer-submitted tickets were invisible to staff. Customer tickets are visible in /support/admin/tickets and /support/agent/tickets — the new banner just makes that path obvious from the customer view.

Backwards compatibility

Both additions are forward-compatible. The Users page assumes hosts are using the is_admin / is_agent columns the install command suggests; hosts wiring the gates differently (Spatie roles, etc.) can override the controller in the companion Laravel package.

Full Changelog: https://github.com/escalated-dev/escalated/compare/v0.7.1...v0.8.0

View on GitHub
v1.3.0Escalated for Laravel

v1.3.0 - Admin users-management page

Added

  • Admin → Users page (#94): new Admin/UserController exposes the host User table (paged, searchable) plus a PATCH /admin/users/{user}/role endpoint that flips one role at a time. Companion Vue page lands in escalated@v0.8.0. Surfaced from escalated#60, where the reporter asked "how do you create agent?" — admins no longer have to drop into tinker to grant or revoke staff access.

    Demoting yourself from admin is rejected server-side so an admin cannot lock themselves out of the panel they're using. Demoting an admin via the agent toggle revokes both flags in one step.

Notes

The default install pins this to the is_admin / is_agent columns the install command tells hosts to add. Hosts wiring the gates differently (Spatie roles, custom pivots) should override Admin/UserController in their own routes — there's a comment to that effect in the class.

Upgrade

composer update escalated-dev/escalated-laravel

No new migrations. The Users nav entry shows up automatically once you upgrade @escalated-dev/escalated to 0.8.0.

Full Changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.2.5...v1.3.0

View on GitHub
v1.2.5Escalated for Laravel

Highlights

Resolves the install failure reported in #88 for real this time (#92)

The actual underlying issue (surfaced thanks to verbose migrate output): MariaDB and older MySQL versions reject escalated_macros's foreign key to users(id) with errno: 150 "Foreign key constraint is incorrectly formed" whenever the host app's users table doesn't exactly match what we expect. Reproduced on MariaDB 11 under four common host configurations:

  • users table on MyISAM (legacy Laragon / WAMP default_storage_engine=MyISAM)
  • users.id is signed BIGINT (project upgraded from Laravel pre-5.8)
  • users.id is INT UNSIGNED (project from pre-5.8 increments())
  • users.id is CHAR(36) UUID (custom user model with HasUuids)

We can't control any of those four from a package, so this release drops the foreign-key constraints from the five migrations that pointed at the host's users table:

  • escalated_macros.created_by
  • escalated_ticket_followers.user_id
  • escalated_saved_views.user_id
  • escalated_mentions.user_id
  • escalated_tickets.snoozed_by

Each becomes a plain unsignedBigInteger column. Foreign keys to our own tables (e.g. escalated_tickets) stay — those always work because we own both sides.

This is the same pattern spatie/laravel-permission, spatie/laravel-activitylog, and Filament use for the same reason.

Better diagnostics on install (#91)

escalated:install was using callSilently('migrate') wrapped in a task component, which swallowed Laravel's actual SQL error and just printed FAIL. Now it prints the full migrate output (per-migration timing, failing migration name, full PDO/SQL error). Same treatment for db:seed. Took a back-and-forth with the reporter to get the real error before — won't happen again.

Backwards compatibility

Forward-compatible only. Anyone with a successful prior install keeps their existing FK constraints because Laravel doesn't re-run migrations once recorded. Anyone hit by #88 sees migrations 14+ run cleanly on their next php artisan migrate.

If a host app hard-deletes users, orphaned created_by / user_id rows in our tables won't be cascade-cleaned anymore (most apps soft-delete users so this is a non-issue in practice). If you need cleanup, attach a User-deleting model observer in the host app.

Upgrade

composer update escalated-dev/escalated-laravel
php artisan escalated:install

Full Changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.2.4...v1.2.5

View on GitHub
v1.2.4Escalated for Laravel

Highlights

Install translations now actually flow from escalated-dev/locale (#90)

The central escalated-dev/locale package was already declared as a composer dep but wasn't actually serving any translations — its JSON uses camelCase keys (matching the Vue frontend) while every PHP-side __() call used snake_case, so the central package was effectively dead weight and translations came entirely from the package's own resources/lang/ PHP fallback.

This release wires the install command to actually pull from central:

  • New JSON-to-Translator bridge in EscalatedServiceProvider (bridgeCentralGroups()) reads each <locale>.json from escalated-dev/locale and injects the nested data into Laravel's translator under the escalated namespace via addLines(). After the bridge fires, __('escalated::commands.install.publishingConfig') resolves against the central JSON. The PHP fallback's commands.php is no longer loaded for the commands group in production — central is the single source of truth.
  • The bridge auto-converts central's {placeholder} syntax (matches the Vue t() helper) to Laravel's :placeholder convention on the way in, so call sites stay idiomatic.
  • All escalated::commands.install.* keys converted from snake_case to camelCase in InstallCommand.php and in every locale's commands.php fallback. The fallback now serves as a parity-with-central offline backup for the dev path.
  • Composer constraint bumped to escalated-dev/locale: ^0.1.8 so the new install keys (seedingPermissions, runningMigrations, runMigrationsConfirm, stepSeed from escalated-locale v0.1.8) are guaranteed present.

Scope (and what's still local)

Only the commands group is bridged today. Other groups — enums, notifications, emails, messages, validation — continue to load from resources/lang/ because their snake_case enum-value keys (e.g. enums.status.in_progress) don't yet have camelCase parity in central. Migrating those is a separate follow-up since enum values are persisted in user databases.

Backwards compatibility

  • Composer install with v0.1.8 central: install command translations resolve via central (production path).
  • Dev/offline scenarios without composer: bridge resolver returns null, install command falls back to the package's resources/lang/ PHP files (now camelCase, matching the call sites).
  • Host-app overrides at lang/vendor/escalated/{locale}/commands.php still work via Laravel's loadNamespaceOverrides mechanism (unchanged).
  • No public API changed — internal i18n plumbing only.

Test plan

  • New tests/Unit/CentralLocaleBridgeTest.php (3 tests, 23 assertions) locks in the bridge: populates $loaded['escalated']['commands'], resolved values match central JSON exactly across en and fr, and {placeholder}:placeholder rewriting works.
  • Full suite: 609 passed.

Full Changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.2.3...v1.2.4

View on GitHub
v1.2.3Escalated for Laravel

Highlights

escalated:install no longer breaks on a clean install (#89, resolves #88)

Previously the command published migration files and then immediately tried to seed escalated_permissions — a table that hadn't been created yet, causing every fresh install to fail with SQLSTATE[42S02] Table doesn't exist. The command now prompts to run migrations + seed default permissions in the correct order, and the printed setup instructions are state-aware (they drop the migrate / seed steps when the user opted into auto-running them).

Also adds the missing seeding_permissions translation key across all 14 locales, fixes a __() call that was silently passing a fallback string into the $locale slot, and adds new running_migrations, run_migrations_confirm, step_seed keys for the new prompts.

Migration / index fixes

  • fix(migration): assign explicit name to delayed_actions index (#87) — pre-empts the Identifier name too long MySQL error for users with a custom escalated.table_prefix longer than 13 characters.
  • fix(migration): assign explicit name to ticket_links unique index (#86, thanks @matalaweb) — fixes the same class of bug for the default escalated_ prefix on MySQL/PostgreSQL.

Other changes since v1.2.2

  • feat(i18n): consume central translations from @escalated-dev/locale (#85)
  • chore(deps): modernize host-app frontend (Vite 8 + Tailwind 4) (#84)
  • Various dependabot updates

Backwards compatibility

All changes in this release are backwards-compatible. The migration index renames in #86 / #87 only take effect on fresh installs — anyone who already ran the original migrations keeps the old auto-generated index names (Laravel doesn't re-run migrations).

Full Changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.2.2...v1.2.3

View on GitHub
v0.7.1Escalated

v0.7.1 — Inertia v3 + configurable widget path

Changed

  • peerDependencies.\"@inertiajs/vue3\" widened from ^1.0.0 || ^2.0.0 to ^1.0.0 || ^2.0.0 || ^3.0.0. Host apps on @inertiajs/vue3@3.x no longer trip an npm install peer-dep conflict (#39). We import only Link, router, useForm, and usePage, all stable across the v1 → v2 → v3 line.

Fixed

  • Widget's API endpoint path is now configurable via data-widget-path (on the script tag) / widgetPath option (on createEscalated). Default stays /support/widget for backward compatibility. Unblocks NestJS hosts where the base path isn't /support (#35).
  • useChat() threads the resolved widgetPath through all six chat API endpoints; Agent TicketShow, ActiveChatsPanel, ChatQueue read page.props.escalated?.prefix to build the right path on the agent side.

Full Changelog: https://github.com/escalated-dev/escalated/compare/v0.7.0...v0.7.1

View on GitHub
v1.2.2Escalated for Laravel

Fixed

  • Customer priority filter now actually filters (#66, fixes #64) — Customer\TicketController@index was dropping the priority URL param (and several other visible filters) before calling the driver. The shared TicketFilters.vue component still rendered a priority dropdown, so the param landed in the URL but never reached a where clause. Allow-list now matches every key the UI sends: status, priority, ticket_type, search, tag, has_attachments, created_after, created_before, plus sort controls. Agent/admin/api paths were never affected — regression tests added for all three to prevent drift.

  • Graceful fallback when the host's users table has no name column (#65, fixes #63) — ticket search (Ticket::scopeSearch + LocalDriver requester filter) assumed a name column existed on the host's user model and would either error against Postgres (column users.name does not exist) or silently return empty against sqlite. The new Escalated::applyUserSearch() helper now consults config('escalated.user_display_column'), checks Schema::hasColumn once per process, and silently degrades to email-only search when the column doesn't exist. Hosts with split first_name/last_name columns now work out of the box; setting ESCALATED_USER_DISPLAY_COLUMN=first_name gives them full first_name+email search.

  • Install command no longer duplicates migrations (#62, fixes #61) — re-running php artisan escalated:install (e.g. during package upgrades) previously republished every migration with a fresh timestamp prefix, leaving the host with duplicate *_create_escalated_*_table.php files and a broken migrate run. Install now globs for existing *_create_escalated_*_table.php files and skips publication if any are present; --force deletes the stale copies first before republishing.

Full changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.2.1...v1.2.2

View on GitHub
v1.2.1Escalated for Laravel

Fixed

  • Postgres compatibility for the Reports endpoint (#60, fixes #59) — ReportingService and ReportController::avgFirstResponseHours previously emitted MySQL-only TIMESTAMPDIFF(HOUR, …) and DATE_FORMAT(…) SQL, which Postgres rejects with column "hour" does not exist. Helpers now match on the driver across sqlite | pgsql | mysql and emit EXTRACT(EPOCH FROM (to - from)) / 3600 / to_char(…, 'YYYY-MM') etc. on Postgres. 16 new unit tests assert exact SQL per driver.

Internal

  • New Docker dev/demo environment under docker/ (#58). docker compose up --build from docker/ boots a 3-container Postgres-backed Laravel host (PHP 8.3 + Postgres 16 + Mailpit) with the package installed and a /demo click-to-login picker. Excluded from the Composer dist via archive.exclude — won't appear in vendor/.
  • Added Escalated\Laravel\Database\Factories\ to the production PSR-4 autoload so Model::factory() resolves at runtime in real installs (same class of bug as #55).

Full changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.2.0...v1.2.1

View on GitHub
v1.2.0Escalated for Laravel

Security

  • SSRF: WorkflowEngine::actionSendWebhook() now validates URL scheme (http/https only) and rejects URLs that resolve to private/reserved IPs (#49)
  • ReDoS: regex injection blocked in compareValues() matches operator via safeRegexMatch() with pattern validation and a PCRE backtrack limit of 10,000 (#49)
  • Action-type injection: strict in: validation for actions.*.type in WorkflowController store/update (#49)
  • Field-access whitelist: resolveFieldValue() default case now whitelists subject, description, ticket_type, channel instead of open $ticket->{$field} access (#49)
  • Rate limiting: ticket creation 5/min, chat start 5/min, chat message 30/min (#49)
  • Audit log: workflow create/update/delete and report exports now produce AuditLog entries (#49)

Fixed

  • php artisan escalated:install no longer aborts with Target class [Escalated\Laravel\Database\Seeders\PermissionSeeder] does not exist — the seeder namespace is now registered in production autoload (#56, fixes #55)
  • Attachment serialization now includes url (#50)
  • Ticket serialization includes computed fields (#51), chat / context panel / activity fields (#52), and the previously missing workflow / workflow log computed fields (#54)
  • Expensive computed fields moved off $appends so list endpoints stay fast; now only included in detail serialization (#53)

Internal

  • CI: minimum-stability set to stable and audit.ignore added for two phpunit advisories that were preventing the resolver from selecting any compatible phpunit version (#57)

Full changelog: https://github.com/escalated-dev/escalated-laravel/compare/v1.1.0...v1.2.0

View on GitHub
v0.7.0Escalated

See CHANGELOG.md for full details.

View on GitHub
v1.1.0Escalated for Laravel

What's Changed

Model-scoped ticket operations (#25)

  • Moved ticket operations (status transitions, assign/unassign, priority, department, replies, activity logging) into Ticket model scope
  • Use mass assignment for ticket/reply creation
  • Fire TicketCreated/TicketUpdated via model hooks, ReplyCreated/InternalNoteAdded via Reply::booted()
  • Added LogTicketStatusChange listener

Reference generation at model creation (#30)

  • Centralized reference generation in model boot hooks — no more scattered TEMP + saveQuietly patterns
  • Auto-generates reference if not explicitly provided
  • Auto-sets status to Open if not provided

Bug fixes (#25, #26, #31)

  • Fix TicketCreated event timing — listeners now receive the final ESC-XXXXX reference, not TEMP-{uuid}
  • Fix double-dispatch of InternalNoteAdded
  • Fix markEscalated() incorrectly setting resolved_at
  • Fix variable shadowing in assign() and changeDepartment() error messages
  • Restore TicketPriority::from() cast in inbound email guest ticket creation
  • Register LogTicketStatusChange listener in service provider
  • Add Notifiable trait to HasTickets

Housekeeping

  • PSR-12 code style fixes
  • Removed accidental package-lock.json
View on GitHub
v1.0.0Escalated for Laravel

Escalated for Laravel v1.0.0

The first stable release of Escalated for Laravel — a full-featured, embeddable support ticket system.

Highlights

  • Laravel 13 + Inertia v3 support with backwards compatibility for Laravel 11/12 and Inertia v2
  • Core-only boot mode — run Escalated without the Inertia UI (API/headless)
  • 6 critical security vulnerabilities fixed
  • 36 new features since v0.6.0

New Features

Ticket Management

  • Custom Fields & Forms — define custom fields per ticket type with conditional logic
  • Custom Statuses — configurable ticket status workflows beyond open/closed
  • Ticket Merging — merge duplicate tickets into a single thread
  • Problem/Incident Linking — link related tickets as problem/incident pairs
  • Side Conversations — private threads within a ticket for internal collaboration
  • Ticket Type Categorization — categorize tickets with automation support
  • Advanced Search — expanded search to requester, with advanced filter params

Agent Workflows

  • Custom Agent Roles / RBAC — granular role-based access control for agents
  • Agent Collision Detection — prevent multiple agents from working the same ticket
  • Light Agents — view-only agent access for stakeholders
  • Skills-Based Routing — route tickets to agents based on skill matching
  • Agent Capacity Management — set and enforce agent workload limits
  • Configurable User Display Column — choose which user field shows in agent selects

Automation & Rules

  • Time-Based Automations — trigger actions based on time conditions (idle tickets, SLA breach)
  • Outbound Webhooks — send ticket events to external services
  • Escalation Rule Categories — organize escalation rules by category

Knowledge Base

  • Knowledge Base — full KB with articles, categories, and controllers

Reporting & Analytics

  • Enhanced Reporting — ReportingService with expanded report types
  • CSAT Settings — customer satisfaction survey configuration

Authentication & Security

  • Two-Factor Authentication — TOTP-based 2FA for agents
  • SSO Service — single sign-on integration with SAML validation
  • JWT & DKIM Validation — token and email authentication verification
  • 6 Critical Security Fixes — patched vulnerabilities in input validation, auth, and data exposure

Data Management

  • Import System — CLI command + admin UI for importing tickets, users, and settings
  • ImportService — orchestrator with entity persistence, resumability, and event suppression
  • Audit Log — full audit trail of all actions

Platform & Extensibility

  • Plugin Bridge — JSON-RPC communication with SDK-based plugins
  • Plugin Marketplace Commandartisan escalated:plugins for discovering plugins
  • Business Hours & Schedules — define working hours for SLA calculation
  • Granular Permission Seeder — default roles with fine-grained permissions
  • Show/Hide Powered By — configurable branding setting

Framework Support

  • Laravel 13 + Inertia v3 — full support while maintaining backwards compat
  • Core-Only Boot Mode — run without Inertia UI for API-only deployments
  • Configurable Table Prefix — consistent prefix support across all migrations

Bug Fixes

  • Register LogTicketStatusChange listener for TicketStatusChanged event
  • Resolve bugs in model functions PR
  • Pass $prefix to Schema::create closure via use() keyword
  • Fix missing use ($prefix) in knowledge base migration closure
  • Update Inertia render path for Plugins page
  • Prevent false positive trait detection in addHasTicketsTrait

Upgrading from v0.6.0

composer require escalated-dev/escalated-laravel:^1.0
php artisan migrate
php artisan escalated:install --force

Review the new config options in config/escalated.php after upgrading.

Full Changelog: https://github.com/escalated-dev/escalated-laravel/compare/v0.6.0...v1.0.0

View on GitHub