PHPackages                             escalated-dev/escalated-laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. escalated-dev/escalated-laravel

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

escalated-dev/escalated-laravel
===============================

An embeddable support ticket system for Laravel applications

v1.4.1(3w ago)262.8k↑2046.7%6[1 issues](https://github.com/escalated-dev/escalated-laravel/issues)1MITPHPPHP ^8.2CI failing

Since Feb 8Pushed 2w ago3 watchersCompare

[ Source](https://github.com/escalated-dev/escalated-laravel)[ Packagist](https://packagist.org/packages/escalated-dev/escalated-laravel)[ RSS](/packages/escalated-dev-escalated-laravel/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (10)Dependencies (32)Versions (78)Used By (1)

 [العربية](docs/translations/README.ar.md) • [Deutsch](docs/translations/README.de.md) • **English** • [Español](docs/translations/README.es.md) • [Français](docs/translations/README.fr.md) • [Italiano](docs/translations/README.it.md) • [日本語](docs/translations/README.ja.md) • [한국어](docs/translations/README.ko.md) • [Nederlands](docs/translations/README.nl.md) • [Polski](docs/translations/README.pl.md) • [Português (BR)](docs/translations/README.pt-BR.md) • [Русский](docs/translations/README.ru.md) • [Türkçe](docs/translations/README.tr.md) • [简体中文](docs/translations/README.zh-CN.md)

Escalated for Laravel
=====================

[](#escalated-for-laravel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/32d2f4957ad92a9148c94c50bddb3d49cf57f47b2585dc54f2c037de8b7f43fb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f657363616c617465642d6465762f657363616c617465642d6c61726176656c2e737667)](https://packagist.org/packages/escalated-dev/escalated-laravel)[![Tests](https://github.com/escalated-dev/escalated-laravel/actions/workflows/laravel.yml/badge.svg)](https://github.com/escalated-dev/escalated-laravel/actions/workflows/laravel.yml)[![FOSSA Status](https://camo.githubusercontent.com/340c0ee30bf8905a48b3429a81b66804ad0de621bb1b06ae07694a938e2a8c21/68747470733a2f2f6170702e666f7373612e636f6d2f6170692f70726f6a656374732f637573746f6d25324236323130372532466769746875622e636f6d253246657363616c617465642d646576253246657363616c617465642d6c61726176656c2e7376673f747970653d736869656c64)](https://app.fossa.com/projects/custom%2B62107%2Fgithub.com%2Fescalated-dev%2Fescalated-laravel?ref=badge_shield)[![Laravel](https://camo.githubusercontent.com/c9a2b1b0dff98b61cd2a56d8d565e6fb8b7070719818f8bc1886bcf1b3aa7092/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c61726176656c2d31312e782d2d31332e782d4646324432303f6c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465)](https://laravel.com/)[![PHP](https://camo.githubusercontent.com/8223298094ca233970d6dc74531e69abde0dd6bd789c7bbb5def179f8461711f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e322b2d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://www.php.net/)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](https://opensource.org/licenses/MIT)

A full-featured, embeddable support ticket system for Laravel. Drop it into any app — get a complete helpdesk with SLA tracking, escalation rules, agent workflows, and a customer portal. No external services required.

> **[escalated.dev](https://escalated.dev)** — Learn more, view demos, and compare Cloud vs Self-Hosted options.

**Three hosting modes.** Run entirely self-hosted, sync to a central cloud for multi-app visibility, or proxy everything to the cloud. Switch modes with a single config change.

Features
--------

[](#features)

- **Ticket lifecycle** — Create, assign, reply, resolve, close, reopen with configurable status transitions
- **SLA engine** — Per-priority response and resolution targets, business hours calculation, automatic breach detection
- **Escalation rules** — Condition-based rules that auto-escalate, reprioritize, reassign, or notify
- **Agent dashboard** — Ticket queue with filters, bulk actions, internal notes, canned responses
- **Customer portal** — Self-service ticket creation, replies, and status tracking
- **Admin panel** — Manage departments, SLA policies, escalation rules, tags, and view reports
- **File attachments** — Drag-and-drop uploads with configurable storage and size limits
- **Activity timeline** — Full audit log of every action on every ticket
- **Email notifications** — Configurable per-event notifications with webhook support
- **Department routing** — Organize agents into departments with auto-assignment (round-robin)
- **Tagging system** — Categorize tickets with colored tags
- **Guest tickets** — Anonymous ticket submission with magic-link access via guest token
- **Inbound email** — Create and reply to tickets via email (Mailgun, Postmark, AWS SES, IMAP)
- **Inertia.js + Vue 3 UI** — Shared frontend via [`@escalated-dev/escalated`](https://github.com/escalated-dev/escalated)
- **Ticket splitting** — Split a reply into a new standalone ticket while preserving the original context
- **Ticket snooze** — Snooze tickets with presets (1h, 4h, tomorrow, next week); `escalated:wake-snoozed-tickets` Artisan command auto-wakes them on schedule
- **Saved views / custom queues** — Save, name, and share filter presets as reusable ticket views
- **Embeddable support widget** — Lightweight `` widget served via `/support/widget/*` routes with KB search, ticket form, and status check
- **Email threading** — Outbound emails include proper `In-Reply-To` and `References` headers for correct threading in mail clients
- **Branded email templates** — Configurable logo, primary color, and footer text for all outbound emails
- **Real-time broadcasting** — Opt-in broadcasting via Pusher, Reverb, or Soketi with automatic polling fallback
- **Knowledge base toggle** — Enable or disable the public knowledge base from admin settings
- **CI: Laravel Pint** — Automated code style enforcement on every pull request

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11.x, 12.x, or 13.x
- Node.js 18+ (for frontend assets)

Quick Start
-----------

[](#quick-start)

```
composer require escalated-dev/escalated-laravel
npm install @escalated-dev/escalated
php artisan escalated:install
php artisan migrate
```

The install command will offer to automatically configure your User model with the `Ticketable` interface and `HasTickets` trait. If you prefer to do this manually, or if you use a custom user model, add the following:

```
use Escalated\Laravel\Contracts\HasTickets;
use Escalated\Laravel\Contracts\Ticketable;

class User extends Authenticatable implements Ticketable
{
    use HasTickets;
}
```

Define authorization gates in `App\Providers\AppServiceProvider::boot()` for Laravel 12+, or `App\Providers\AuthServiceProvider::boot()` for Laravel 11 and earlier:

```
use Illuminate\Support\Facades\Gate;

Gate::define('escalated-admin', fn ($user) => $user->is_admin);
Gate::define('escalated-agent', fn ($user) => $user->is_agent);
```

Visit `/support` — you're live.

### UUID / string user keys

[](#uuid--string-user-keys)

Escalated supports both integer and string (UUID/ULID) host-app user keys out of the box — no code or migration edits required:

- Every user-id parameter is typed `int|string`, and incoming user ids are never cast to `int` (so UUIDs aren't corrupted).
- The user-referencing columns Escalated creates (`ticket_followers.user_id`, `agent_profiles.user_id`, `tickets.assigned_to`/`requester_id`, the polymorphic requester/author/causer columns, the role and skill pivots, etc.) are typed to **match your user model's key type automatically**. At migration time Escalated reflects the configured `user_model`: an auto-incrementing integer key yields `unsignedBigInteger` columns; a `HasUuids`/`HasUlids`/string key yields string-compatible columns.

Override the detection with the `user_key_type` config (`'auto'` by default; `'bigint'`, `'uuid'`, `'ulid'`, or `'string'`):

```
// config/escalated.php
'user_key_type' => 'uuid',
```

> **Existing installs:** the column type is chosen when a migration runs. Apps already migrated (e.g. as `bigint`) keep their columns; switching your user key type after installing requires a manual migration.

Ticket subjects
---------------

[](#ticket-subjects)

A ticket has a **requester** (the person who raised it) and a **subject line**(free text). Sometimes a ticket is also *about* one or more host-app entities — a Project, a Customer, an asset — that aren't people. Attach them as ticket **subjects** so agents see what the ticket concerns and can jump straight to it in your app.

Make any model attachable by implementing the `TicketSubject` contract. The `PresentsAsTicketSubject` trait gives sane defaults — override only what you need:

```
use Escalated\Laravel\Concerns\PresentsAsTicketSubject;
use Escalated\Laravel\Contracts\TicketSubject;

class Project extends Model implements TicketSubject
{
    use PresentsAsTicketSubject;

    public function ticketSubjectSubtitle(): ?string
    {
        return 'Project · '.$this->customer->name;
    }

    public function ticketSubjectUrl(): ?string
    {
        return route('projects.show', $this);
    }

    public function ticketSubjectColor(): ?string
    {
        return '#2563eb';
    }

    public function ticketSubjectIcon(): ?string
    {
        return 'folder';
    }
}
```

Attach, detach, or sync subjects on a ticket — a ticket can reference several:

```
$ticket->attachSubject($project, role: 'project');
$ticket->attachSubject($customer, role: 'account');
$ticket->syncSubjects([$project, [$customer, 'account']]);
$ticket->detachSubject($project);
```

Each attached subject is serialized onto the ticket as `{ type, id, role, title, subtitle, url, color, icon }` for the frontend to render (clickable when `url` is set). `subject_id` is stored as a string, so integer, UUID, or string-keyed host models all work.

To allow attaching subjects via the agent API (and protect against arbitrary class resolution from request input), list the permitted models in config:

```
// config/escalated.php
'ticket_subjects' => [
    'types' => [
        \App\Models\Project::class,
        \App\Models\Customer::class,
    ],
],
```

Programmatic `attachSubject()` works for any model when the allowlist is empty; the agent API only accepts allowlisted types.

Frontend Integration
--------------------

[](#frontend-integration)

Escalated ships a Vue component library and default pages via the [`@escalated-dev/escalated`](https://github.com/escalated-dev/escalated) npm package.

### 1. Tailwind Content

[](#1-tailwind-content)

For Tailwind CSS v3 and earlier, add the Escalated package to your Tailwind `content` config so its classes aren't purged:

```
// tailwind.config.js
content: [
    // ... your existing paths
    './node_modules/@escalated-dev/escalated/src/**/*.vue',
],
```

For Tailwind CSS v4+, add Escalated as a source in your app CSS file instead:

```
/* resources/css/app.css */
@source '../../node_modules/@escalated-dev/escalated/src/**/*.vue';
```

Adjust the relative path if your CSS file lives somewhere else.

### 2. Page Resolver

[](#2-page-resolver)

If your app already has Inertia and Vue configured, add the Escalated page resolver to your existing `app.ts`:

```
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

const escalatedPages = import.meta.glob(
    '../../node_modules/@escalated-dev/escalated/src/pages/**/*.vue',
);

createInertiaApp({
    resolve: (name) => {
        if (name.startsWith('Escalated/')) {
            const path = name.replace('Escalated/', '');
            return resolvePageComponent(
                `../../node_modules/@escalated-dev/escalated/src/pages/${path}.vue`,
                escalatedPages,
            );
        }
        return resolvePageComponent(`./Pages/${name}.vue`,
            import.meta.glob('./Pages/**/*.vue'));
    },
    // ...
});
```

### 3. Theming (Optional)

[](#3-theming-optional)

Register the `EscalatedPlugin` to render Escalated pages inside your app's layout — no page duplication needed:

```
import { EscalatedPlugin } from '@escalated-dev/escalated';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';

createInertiaApp({
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(EscalatedPlugin, {
                layout: AuthenticatedLayout,
            })
            .mount(el);
    },
});
```

Your layout component must accept a `#header` slot and a default slot. Escalated will render its sub-navigation in the header and page content in the default slot.

Without the plugin, Escalated uses its own standalone layout with a simple nav bar.

### CSS Custom Properties

[](#css-custom-properties)

Pass a `theme` option to customize colors and radii:

```
app.use(EscalatedPlugin, {
    layout: AuthenticatedLayout,
    theme: {
        primary: '#3b82f6',
        radius: '0.75rem',
    }
})
```

PropertyDefaultDescription`--esc-primary``#4f46e5`Primary action color`--esc-primary-hover`auto-darkenedPrimary hover color`--esc-radius``0.5rem`Border radius for inputs and buttons`--esc-radius-lg`auto-scaledBorder radius for cards and panels`--esc-font-family`inheritFont family override### Available Components

[](#available-components)

ComponentDescription`ActivityTimeline`Full audit log of ticket events`AssigneeSelect`Agent assignment dropdown`AttachmentList`File attachment display`FileDropzone`Drag-and-drop file upload`PriorityBadge`Priority level indicator`ReplyComposer`Rich text reply editor`ReplyThread`Chronological message thread`SlaTimer`SLA countdown display`StatsCard`Metric card for dashboards`StatusBadge`Ticket status indicator`TagSelect`Tag picker with colors`TicketFilters`Search and filter controls`TicketList`Paginated ticket table`TicketSidebar`Ticket metadata sidebar### Shared Inertia Props

[](#shared-inertia-props)

Escalated automatically shares data to all Inertia pages via `page.props.escalated`:

```
page.props.escalated = {
    prefix: 'support',     // Route prefix from config
    is_agent: true,        // Current user can access agent views
    is_admin: false,       // Current user can access admin views
}
```

Use these to conditionally show nav links or restrict UI elements.

Hosting Modes
-------------

[](#hosting-modes)

### Self-Hosted (default)

[](#self-hosted-default)

Everything stays in your database. No external calls. Full autonomy.

```
// config/escalated.php
'mode' => 'self-hosted',
```

### Synced

[](#synced)

Local database + automatic sync to `cloud.escalated.dev` for unified inbox across multiple apps. If the cloud is unreachable, your app keeps working — events queue and retry.

```
'mode' => 'synced',
'hosted' => [
    'api_url' => 'https://cloud.escalated.dev/api/v1',
    'api_key' => env('ESCALATED_API_KEY'),
],
```

### Cloud

[](#cloud)

All ticket data proxied to the cloud API. Your app handles auth and renders UI, but storage lives in the cloud. Supports multiple domains per API key.

```
'mode' => 'cloud',
```

All three modes share the same controllers, UI, and business logic. The driver pattern handles the rest.

Publishing Assets
-----------------

[](#publishing-assets)

```
# Email templates
php artisan vendor:publish --tag=escalated-views

# Config file
php artisan vendor:publish --tag=escalated-config

# Database migrations
php artisan vendor:publish --tag=escalated-migrations

# Translations (sourced from the central escalated-dev/locale package)
php artisan vendor:publish --tag=escalated-lang
```

Translations
------------

[](#translations)

Translation strings for the `escalated` namespace are sourced from the shared [`escalated-dev/locale`](https://github.com/escalated-dev/escalated-locale)Composer package, which is consumed by every Escalated host plugin (Laravel, Rails, NestJS, Django, …) so wording stays consistent across backends.

The `EscalatedServiceProvider` wires three layers, lowest to highest precedence:

1. **Central package** — `vendor/escalated-dev/locale/locales/{locale}/...`The canonical source.
2. **Plugin-shipped overrides** — `lang/vendor/escalated/{locale}/...` in this repository, for Laravel-specific divergences.
3. **Host-app overrides** — `{app}/lang/vendor/escalated/{locale}/...`, populated via `php artisan vendor:publish --tag=escalated-lang`. Apps edit those files to brand or relocalize Escalated.

Each layer is merged on top of the previous one with `array_replace_recursive` (PHP groups) and `array_merge` (JSON), so a layer only needs to define the keys it changes. See [`lang/README.md`](lang/README.md) for details.

Scheduling
----------

[](#scheduling)

Add these to your scheduler for SLA and escalation automation:

```
// app/Console/Kernel.php or routes/console.php
Schedule::command('escalated:check-sla')->everyMinute();
Schedule::command('escalated:evaluate-escalations')->everyFiveMinutes();
Schedule::command('escalated:close-resolved')->daily();
Schedule::command('escalated:purge-activities')->weekly();
Schedule::command('escalated:poll-imap')->everyMinute(); // Only if using IMAP adapter
```

Configuration
-------------

[](#configuration)

All config lives in `config/escalated.php`. Key options:

```
'mode' => 'self-hosted',              // self-hosted | synced | cloud
'user_model' => App\Models\User::class,
'table_prefix' => 'escalated_',
'default_priority' => 'medium',

'routes' => [
    'prefix' => 'support',
    'middleware' => ['web', 'auth'],
],

'tickets' => [
    'allow_customer_close' => true,
    'auto_close_resolved_after_days' => 7,
],

'sla' => [
    'enabled' => true,
    'business_hours_only' => false,
    'business_hours' => [
        'start' => '09:00',
        'end' => '17:00',
        'timezone' => 'UTC',
        'days' => [1, 2, 3, 4, 5],
    ],
],
```

See the [full configuration reference](docs/configuration.md).

Events
------

[](#events)

Every ticket action dispatches an event you can listen to:

EventWhen`TicketCreated`New ticket`TicketStatusChanged`Status transition`TicketAssigned`Agent assigned`ReplyCreated`Public reply added`InternalNoteAdded`Agent note added`SlaBreached`SLA deadline missed`TicketEscalated`Ticket escalated`TicketResolved`Ticket resolved`TicketClosed`Ticket closed```
use Escalated\Laravel\Events\TicketCreated;

Event::listen(TicketCreated::class, function ($event) {
    // $event->ticket
});
```

[Full events documentation →](docs/events.md)

Inbound Email
-------------

[](#inbound-email)

Escalated can create and reply to tickets from incoming emails. Supports **Mailgun**, **Postmark**, **AWS SES** webhooks, and **IMAP** polling as a fallback.

### How It Works

[](#how-it-works)

1. An external email service receives an email at your support address (e.g., `support@yourapp.com`)
2. The service forwards the email to your application via webhook (or IMAP polling fetches it)
3. Escalated normalizes the payload into an `InboundMessage` DTO via the adapter
4. The `InboundEmailService` processes the message:
    - **Thread matching**: checks the subject for a ticket reference (e.g., `[ESC-00001]`), then checks `In-Reply-To` / `References` headers against stored message IDs
    - **Match found**: adds a reply to the existing ticket; reopens the ticket if it was resolved or closed
    - **No match**: creates a new ticket — if the sender is a registered user they become the requester, otherwise a guest ticket is created
5. Every inbound email is logged to `escalated_inbound_emails` for audit

### Enable Inbound Email

[](#enable-inbound-email)

```
ESCALATED_INBOUND_EMAIL=true
ESCALATED_INBOUND_ADDRESS=support@yourapp.com
```

### Adapter Setup

[](#adapter-setup)

#### Mailgun

[](#mailgun)

```
ESCALATED_INBOUND_ADAPTER=mailgun
ESCALATED_MAILGUN_SIGNING_KEY=your-mailgun-signing-key
```

Configure a Mailgun Route to forward inbound emails to:

```
POST https://yourapp.com/support/inbound/mailgun

```

The signing key is in your Mailgun dashboard under **Settings &gt; API Keys &gt; HTTP Webhook Signing Key**. Requests are verified via HMAC-SHA256 signature.

#### Postmark

[](#postmark)

```
ESCALATED_INBOUND_ADAPTER=postmark
ESCALATED_POSTMARK_INBOUND_TOKEN=your-postmark-inbound-token
```

Configure an Inbound Webhook in your Postmark server settings pointing to:

```
POST https://yourapp.com/support/inbound/postmark

```

The token is sent in the `X-Postmark-Token` header and verified on each request.

#### AWS SES

[](#aws-ses)

```
ESCALATED_INBOUND_ADAPTER=ses
ESCALATED_SES_REGION=us-east-1
ESCALATED_SES_TOPIC_ARN=arn:aws:sns:us-east-1:123456789:your-topic
```

1. Configure SES to receive emails and publish to an SNS topic
2. Create an HTTPS subscription on the SNS topic pointing to: ```
    POST https://yourapp.com/support/inbound/ses

    ```
3. Escalated auto-confirms the SNS subscription and verifies message signatures using Amazon's certificate

#### IMAP (Fallback)

[](#imap-fallback)

For providers without webhook support, poll via IMAP:

```
ESCALATED_INBOUND_ADAPTER=imap
ESCALATED_IMAP_HOST=imap.gmail.com
ESCALATED_IMAP_PORT=993
ESCALATED_IMAP_ENCRYPTION=ssl
ESCALATED_IMAP_USERNAME=support@yourapp.com
ESCALATED_IMAP_PASSWORD=your-app-password
ESCALATED_IMAP_MAILBOX=INBOX
```

Schedule the poll command:

```
Schedule::command('escalated:poll-imap')->everyMinute();
```

### Webhook URL

[](#webhook-url)

```
POST /{prefix}/inbound/{adapter}

```

Where `{prefix}` is your configured route prefix (default: `support`) and `{adapter}` is `mailgun`, `postmark`, or `ses`. These routes use the `api` middleware (no CSRF, no auth).

### Processing Features

[](#processing-features)

- **Thread detection** via subject reference pattern (`[ESC-00001]`) and `In-Reply-To` / `References` headers
- **Guest tickets** for unknown senders — display name derived from email (e.g., `john.doe@example.com` → `John Doe`)
- **Subject sanitization** — strips `RE:`, `FW:`, `FWD:` prefixes (including stacked)
- **HTML fallback** — uses stripped HTML body when plain text is empty
- **Duplicate detection** — skips messages with duplicate `Message-ID` headers
- **Attachment handling** — stores attachments respecting `max_attachment_size_kb` and `max_attachments_per_reply`
- **Auto-reopen** — reopens resolved/closed tickets when a reply arrives via email
- **Audit logging** — every inbound email recorded in `escalated_inbound_emails` with status tracking

### Custom Adapter

[](#custom-adapter)

Implement the `InboundAdapter` interface:

```
use Escalated\Laravel\Mail\Adapters\InboundAdapter;
use Escalated\Laravel\Mail\InboundMessage;
use Illuminate\Http\Request;

class MyAdapter implements InboundAdapter
{
    public function parseRequest(Request $request): InboundMessage
    {
        return new InboundMessage(
            fromEmail: $request->input('from'),
            fromName: $request->input('name'),
            toEmail: $request->input('to'),
            subject: $request->input('subject'),
            bodyText: $request->input('text'),
            bodyHtml: $request->input('html'),
            messageId: $request->input('message_id'),
            inReplyTo: $request->input('in_reply_to'),
        );
    }

    public function verifyRequest(Request $request): bool
    {
        return $request->header('X-Secret') === config('services.my_adapter.secret');
    }
}
```

### Inbound Email Environment Variables

[](#inbound-email-environment-variables)

VariableDefaultDescription`ESCALATED_INBOUND_EMAIL``false`Enable inbound email`ESCALATED_INBOUND_ADAPTER``mailgun`Default adapter`ESCALATED_INBOUND_ADDRESS``support@example.com`Support email address`ESCALATED_MAILGUN_SIGNING_KEY`—Mailgun webhook signing key`ESCALATED_POSTMARK_INBOUND_TOKEN`—Postmark inbound token`ESCALATED_SES_REGION``us-east-1`AWS SES region`ESCALATED_SES_TOPIC_ARN`—AWS SNS topic ARN`ESCALATED_IMAP_HOST`—IMAP server hostname`ESCALATED_IMAP_PORT``993`IMAP server port`ESCALATED_IMAP_ENCRYPTION``ssl`IMAP encryption`ESCALATED_IMAP_USERNAME`—IMAP username`ESCALATED_IMAP_PASSWORD`—IMAP password`ESCALATED_IMAP_MAILBOX``INBOX`IMAP mailbox to pollRoutes
------

[](#routes)

RouteMethodDescription`/support`GETCustomer ticket list`/support/create`GETNew ticket form`/support/{ticket}`GETTicket detail`/support/guest/create`GETGuest ticket form`/support/guest/{token}`GETGuest ticket view (magic link)`/support/agent`GETAgent dashboard`/support/agent/tickets`GETAgent ticket queue`/support/agent/tickets/{ticket}`GETAgent ticket view`/support/admin/reports`GETAdmin reports`/support/admin/departments`GETDepartment management`/support/admin/sla-policies`GETSLA policy management`/support/admin/escalation-rules`GETEscalation rule management`/support/admin/tags`GETTag management`/support/admin/canned-responses`GETCanned response management`/support/inbound/mailgun`POSTMailgun inbound webhook`/support/inbound/postmark`POSTPostmark inbound webhook`/support/inbound/ses`POSTSES/SNS inbound webhook`/support/agent/tickets/bulk`POSTBulk actions on multiple tickets`/support/agent/tickets/{ticket}/follow`POSTFollow/unfollow a ticket`/support/agent/tickets/{ticket}/macro`POSTApply a macro to a ticket`/support/agent/tickets/{ticket}/presence`POSTUpdate presence on a ticket`/support/agent/tickets/{ticket}/pin/{reply}`POSTPin/unpin an internal note`/support/{ticket}/rate`POSTSubmit satisfaction ratingAll routes use the configurable prefix (default: `support`). Inbound webhook routes use the `api` middleware (no auth, no CSRF).

Plugin SDK
----------

[](#plugin-sdk)

Escalated supports framework-agnostic plugins built with the [Plugin SDK](https://github.com/escalated-dev/escalated-plugin-sdk). Plugins are written once in TypeScript and work across all Escalated backends.

### Installing Plugins

[](#installing-plugins)

The plugin bridge is built into `escalated-laravel` — no additional PHP package required. Install plugins and the runtime via npm:

```
npm install @escalated-dev/plugin-runtime
npm install @escalated-dev/plugin-slack
npm install @escalated-dev/plugin-jira
```

### Enabling SDK Plugins

[](#enabling-sdk-plugins)

```
// config/escalated.php
'plugins' => [
    'enabled'     => true,
    'sdk_enabled' => true,  // Enable the Node.js bridge
],
```

### How It Works

[](#how-it-works-1)

SDK plugins run as a Node.js subprocess managed by `@escalated-dev/plugin-runtime`, communicating with Laravel over JSON-RPC 2.0 via stdio. The `escalated_do_action()` and `escalated_apply_filters()` helpers dual-dispatch to both legacy PHP plugins and new SDK plugins simultaneously — no changes to existing hook call sites.

### Building Your Own Plugin

[](#building-your-own-plugin)

```
import { definePlugin } from '@escalated-dev/plugin-sdk'

export default definePlugin({
  name: 'my-plugin',
  version: '1.0.0',
  actions: {
    'ticket.created': async (event, ctx) => {
      ctx.log.info('New ticket!', event)
    },
  },
})
```

### Resources

[](#resources)

- [Plugin SDK](https://github.com/escalated-dev/escalated-plugin-sdk) — TypeScript SDK for building plugins
- [Plugin Runtime](https://github.com/escalated-dev/escalated-plugin-runtime) — Runtime host for plugins
- [Plugin Development Guide](https://github.com/escalated-dev/escalated-docs) — Full documentation

See the detailed [Plugin Bridge](#plugin-bridge-sdk-plugins) section below for the full architecture, auto-generated routes, dual dispatch, and store documentation.

Plugin Bridge (SDK Plugins)
---------------------------

[](#plugin-bridge-sdk-plugins)

Escalated supports a second generation of plugins written in TypeScript using the `@escalated-dev/plugin-sdk`. These plugins run as a Node.js subprocess managed by `@escalated-dev/plugin-runtime` and communicate with Laravel over JSON-RPC 2.0 via stdio.

### How It Works

[](#how-it-works-2)

```
Laravel (PHP)                     Plugin Runtime (Node.js)
┌──────────────────────┐  stdio   ┌──────────────────────┐
│ PluginBridge         │◄────────►│ @escalated-dev/       │
│  - spawns subprocess │  JSON-   │   plugin-runtime      │
│  - dispatches hooks  │  RPC 2.0 │  ┌────────────────┐   │
│  - handles ctx.*     │          │  │ Slack Plugin    │   │
│  - mounts routes     │          │  │ Jira Plugin     │   │
└──────────────────────┘          │  │ ...             │   │
                                  │  └────────────────┘   │
                                  └──────────────────────┘

```

The bridge spawns the runtime **lazily** on the first hook dispatch and keeps the process alive across requests (one long-lived subprocess per PHP-FPM worker). If the process crashes it is automatically restarted with exponential backoff.

### Requirements

[](#requirements-1)

- Node.js 18+
- `@escalated-dev/plugin-runtime` installed in your project:

```
npm install @escalated-dev/plugin-runtime
```

Install any SDK plugins the same way:

```
npm install @escalated-dev/plugin-slack @escalated-dev/plugin-jira
```

### Startup Sequence

[](#startup-sequence)

1. `EscalatedServiceProvider::boot()` calls `$bridge->boot()`
2. Bridge spawns `node node_modules/@escalated-dev/plugin-runtime/dist/index.js`
3. Protocol handshake confirms version compatibility
4. Bridge fetches the plugin manifest (pages, hooks, endpoints, webhooks)
5. Routes are registered in Laravel for plugin pages, API endpoints, and webhooks
6. Runtime is ready to receive hook dispatches

### Auto-generated Routes

[](#auto-generated-routes)

For each installed SDK plugin the bridge automatically registers:

CategoryURL PatternAuthAdmin pages`{prefix}/admin/plugins/{plugin}/{route}`AdminData endpoints`{prefix}/api/plugins/{plugin}/{path}`AdminWebhook endpoints`{prefix}/webhooks/plugins/{plugin}/{path}`None### Dual Dispatch (Backward Compatibility)

[](#dual-dispatch-backward-compatibility)

The existing `escalated_do_action()` and `escalated_apply_filters()` helper functions dispatch hooks to **both** old PHP plugins and new SDK plugins simultaneously. No changes are required to existing hook call sites.

```
// This automatically dispatches to PHP plugins AND SDK plugins:
escalated_do_action('ticket.created', $ticket->toArray());

// Same for filters:
$channels = escalated_apply_filters('notification.channels', []);
```

### Plugin Store

[](#plugin-store)

SDK plugins can persist data using `ctx.store`. This is backed by the `escalated_plugin_store` table:

```
php artisan vendor:publish --tag=escalated-migrations
php artisan migrate
```

### Configuration

[](#configuration-1)

```
// config/escalated.php
'plugins' => [
    'enabled'         => true,
    'sdk_enabled'     => true,      // Enable the Node.js bridge
    'runtime_command' => 'node node_modules/@escalated-dev/plugin-runtime/dist/index.js',
    'runtime_cwd'     => base_path(), // Working directory for the subprocess
],
```

### Writing SDK Plugins

[](#writing-sdk-plugins)

See the [`@escalated-dev/plugin-sdk`](https://github.com/escalated-dev/plugin-sdk) package for the full TypeScript authoring API. A minimal plugin looks like:

```
import { definePlugin } from '@escalated-dev/plugin-sdk'

export default definePlugin({
  name: 'my-plugin',
  version: '1.0.0',

  actions: {
    'ticket.created': async (event, ctx) => {
      const config = await ctx.config.all()
      // ... do something
    },
  },

  endpoints: {
    'GET /settings': { capability: 'manage_settings', handler: async (ctx) => {
      return await ctx.config.all()
    }},
  },
})
```

Documentation
-------------

[](#documentation)

- [Installation](docs/installation.md)
- [Configuration](docs/configuration.md)
- [Customization](docs/customization.md)
- [Events](docs/events.md)
- [SLA Policies](docs/sla-policies.md)
- [Escalation Rules](docs/escalation-rules.md)
- [Hosting Modes](docs/hosting-modes.md)

Newsletters (optional, disabled by default)
-------------------------------------------

[](#newsletters-optional-disabled-by-default)

An admin-only broadcast feature for sending Markdown emails to contacts. Disabled by default — when off, no newsletter routes, controllers, or scheduler hooks register.

```
ESCALATED_ENABLE_NEWSLETTERS=true
ESCALATED_NEWSLETTER_DEFAULT_FROM=hi@example.com
ESCALATED_NEWSLETTER_DEFAULT_THEME=default
```

Add a Laravel scheduler entry in `app/Console/Kernel.php`:

```
$schedule->command('escalated:newsletters:dispatch')->everyMinute()->withoutOverlapping();
```

Then re-seed permissions to attach `newsletters.manage` and `newsletters.send` to the Admin role:

```
php artisan db:seed --class=Escalated\\Laravel\\Database\\Seeders\\PermissionSeeder
```

Custom themes live in `resources/views/vendor/escalated/newsletters/themes/.blade.php`.

ESP webhook endpoints for outbound events:

- `POST /escalated/webhooks/newsletter/postmark`
- `POST /escalated/webhooks/newsletter/mailgun`
- `POST /escalated/webhooks/newsletter/ses`
- `POST /escalated/webhooks/newsletter/sendgrid`

Testing
-------

[](#testing)

```
composer install
vendor/bin/pest
```

Also Available For
------------------

[](#also-available-for)

- **[Escalated for Laravel](https://github.com/escalated-dev/escalated-laravel)** — Laravel Composer package (you are here)
- **[Escalated for Rails](https://github.com/escalated-dev/escalated-rails)** — Ruby on Rails engine
- **[Escalated for Django](https://github.com/escalated-dev/escalated-django)** — Django reusable app
- **[Escalated for AdonisJS](https://github.com/escalated-dev/escalated-adonis)** — AdonisJS v6 package
- **[Escalated for Filament](https://github.com/escalated-dev/escalated-filament)** — Filament v3 admin panel plugin
- **[Shared Frontend](https://github.com/escalated-dev/escalated)** — Vue 3 + Inertia.js UI components

Same architecture, same Vue UI, same three hosting modes — for every major backend framework.

License
-------

[](#license)

MIT

###  Health Score

56

—

FairBetter than 97% of packages

Maintenance95

Actively maintained with recent releases

Popularity35

Limited adoption so far

Community20

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 86.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

24

Last Release

25d ago

Major Versions

0.6.0 → v1.0.02026-04-06

### Community

Maintainers

![](https://www.gravatar.com/avatar/1f1e8418623e51236a1fd63d1da722d2b15a8a8b067b10d520580fd3e9a6e509?d=identicon)[MatthewGross](/maintainers/MatthewGross)

---

Top Contributors

[![mpge](https://avatars.githubusercontent.com/u/3311227?v=4)](https://github.com/mpge "mpge (224 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (17 commits)")[![matalaweb](https://avatars.githubusercontent.com/u/4079247?v=4)](https://github.com/matalaweb "matalaweb (13 commits)")[![marufmax](https://avatars.githubusercontent.com/u/7222229?v=4)](https://github.com/marufmax "marufmax (4 commits)")[![codearachnid](https://avatars.githubusercontent.com/u/755025?v=4)](https://github.com/codearachnid "codearachnid (2 commits)")

---

Tags

laravellaravelsupportticketshelpdeskcustomer-support

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/escalated-dev-escalated-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/escalated-dev-escalated-laravel/health.svg)](https://phpackages.com/packages/escalated-dev-escalated-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M122](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9742.3M121](/packages/roots-acorn)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1232.2k16](/packages/fleetbase-core-api)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
