An embeddable support ticket system for AdonisJS v6 applications. Drop-in customer support with tickets, SLA management, departments, escalation rules, canned responses, macros, inbound email processing, satisfaction ratings, and a full admin panel -- all rendered via Inertia.js with Vue 3.
- AdonisJS v6 (Core ^6.0)
- @adonisjs/lucid ^21.0
- @adonisjs/auth ^9.0
- @adonisjs/inertia ^1.0
- @adonisjs/drive ^3.0 (for file attachments)
- @adonisjs/mail ^9.0 (optional, for notifications)
- Node.js 20+
npm install @escalated-dev/escalated-adonisThen run the configure command:
node ace configure @escalated-dev/escalated-adonisThis will:
- Publish
config/escalated.tsto your application - Register the
EscalatedProviderin your.adonisrc.ts - Copy all database migration files to your
database/migrations/directory
Run the migrations:
node ace migration:runEdit config/escalated.ts to customize behavior:
const escalatedConfig: EscalatedConfig = {
// Hosting mode: 'self-hosted' | 'synced' | 'cloud'
mode: 'self-hosted',
// Your app's user model import path
userModel: '#models/user',
// Route configuration
routes: {
enabled: true,
prefix: 'support',
middleware: ['auth'],
adminMiddleware: ['auth'],
},
// Authorization callbacks
authorization: {
isAgent: (user) => user.isEscalatedAgent ?? false,
isAdmin: (user) => user.isEscalatedAdmin ?? false,
},
// ... see config file for full options
}The isAgent and isAdmin callbacks determine role-based access. You can use boolean properties, methods, or any async logic:
authorization: {
isAgent: async (user) => {
// Check a database column
return user.role === 'agent' || user.role === 'admin'
},
isAdmin: async (user) => {
return user.role === 'admin'
},
}- Tickets: Create, view, update, close, reopen tickets with status machine
- Replies: Threaded conversations with rich text
- Departments: Organize tickets by team with agent assignments
- Tags: Label and categorize tickets
- SLA Policies: First response and resolution time tracking with breach detection
- Escalation Rules: Automated ticket escalation based on configurable conditions
- Canned Responses: Pre-written reply templates (shared or per-agent)
- Activity Log: Full audit trail of all ticket changes
- Attachments: File uploads on replies with configurable storage
- Notifications: Webhook-based notifications with HMAC signing
- Admin Settings: Runtime-configurable settings stored in the database
- Guest Tickets: Anonymous ticket creation with 64-character token access
- Mailgun Adapter: Receive emails via Mailgun webhooks
- Postmark Adapter: Receive emails via Postmark inbound
- SES Adapter: Receive emails via Amazon SES
- Auto-threading: Emails matched to existing tickets via subject patterns and In-Reply-To headers
- Attachment Processing: Inbound email attachments with blocked extension filtering
- Spam Detection: Mark emails as spam
- Three Role Types: Customer, Agent, Admin with separate dashboards
- Bulk Actions: Batch status/priority/assignment/tag/department changes
- Macros: Multi-step automated actions (status + priority + reply in one click)
- Followers: Subscribe to ticket updates
- CSAT Ratings: Post-resolution satisfaction surveys (1-5 scale)
- Pinned Notes: Pin important replies for quick reference
- Quick Filters: Pre-built filter shortcuts (My Tickets, Unassigned, Breached SLA, etc.)
- Presence Indicators: See who is viewing a ticket in real time
- Enhanced Dashboard: Agent dashboard with workload stats and quick filters
| Model | Table | Description |
|---|---|---|
Ticket |
escalated_tickets |
Core ticket with status machine, guest support |
Reply |
escalated_replies |
Threaded replies with pinning and internal notes |
Department |
escalated_departments |
Team organization with agent pivot |
Tag |
escalated_tags |
Ticket categorization |
SlaPolicy |
escalated_sla_policies |
SLA time targets per priority |
EscalationRule |
escalated_escalation_rules |
Automated escalation conditions/actions |
CannedResponse |
escalated_canned_responses |
Reply templates |
TicketActivity |
escalated_ticket_activities |
Audit log entries |
Attachment |
escalated_attachments |
File attachments (polymorphic) |
Macro |
escalated_macros |
Multi-step action sequences |
SatisfactionRating |
escalated_satisfaction_ratings |
CSAT ratings |
InboundEmail |
escalated_inbound_emails |
Inbound email log |
EscalatedSetting |
escalated_settings |
Key-value runtime settings |
| Service | Description |
|---|---|
TicketService |
Create, update, transition status, reply, add notes, manage tags/departments |
AssignmentService |
Assign/unassign agents, auto-assign (least-workload strategy) |
SlaService |
Attach policies, check breaches, calculate due dates with business hours |
EscalationService |
Evaluate rules, find matching tickets, execute automated actions |
NotificationService |
Send webhook notifications with HMAC-SHA256 signing |
AttachmentService |
Store/delete file attachments via AdonisJS Drive |
MacroService |
Execute multi-step macro actions on tickets |
InboundEmailService |
Process inbound emails, match to tickets, create replies |
| Controller | Role | Description |
|---|---|---|
CustomerTicketsController |
Customer | List, create, view, reply, close, reopen |
AgentDashboardController |
Agent | Dashboard with stats and quick filters |
AgentTicketsController |
Agent | Full ticket management with all actions |
AdminTicketsController |
Admin | Same as agent plus delete capability |
AdminDepartmentsController |
Admin | CRUD for departments |
AdminTagsController |
Admin | CRUD for tags |
AdminSlaPoliciesController |
Admin | CRUD for SLA policies |
AdminEscalationRulesController |
Admin | CRUD for escalation rules |
AdminCannedResponsesController |
Admin | CRUD for canned responses |
AdminMacrosController |
Admin | CRUD for macros |
AdminReportsController |
Admin | Reporting dashboard |
AdminSettingsController |
Admin | Runtime settings management |
BulkActionsController |
Agent/Admin | Batch ticket operations |
SatisfactionRatingController |
Customer | Submit CSAT ratings |
GuestTicketsController |
Public | Anonymous ticket creation and viewing |
InboundEmailController |
Webhook | Process inbound email from adapters |
| Middleware | Description |
|---|---|
EnsureIsAgent |
Verifies user is an agent (or admin) via config callback |
EnsureIsAdmin |
Verifies user is an admin via config callback |
ResolveTicket |
Resolves ticket by reference or ID, attaches to context |
All controllers render Inertia pages with the Escalated/ prefix. Your Vue app must provide these page components:
Escalated/Customer/Index- Ticket listEscalated/Customer/Create- New ticket formEscalated/Customer/Show- Ticket detail with replies
Escalated/Agent/Dashboard- Agent dashboardEscalated/Agent/Tickets/Index- Ticket list with filtersEscalated/Agent/Tickets/Show- Ticket detail with all actions
Escalated/Admin/Tickets/Index- Admin ticket listEscalated/Admin/Tickets/Show- Admin ticket detailEscalated/Admin/Departments/Index- Departments listEscalated/Admin/Departments/Create- New departmentEscalated/Admin/Departments/Edit- Edit departmentEscalated/Admin/Tags/Index- Tags managementEscalated/Admin/SlaPolicies/Index- SLA policies listEscalated/Admin/SlaPolicies/Create- New SLA policyEscalated/Admin/SlaPolicies/Edit- Edit SLA policyEscalated/Admin/EscalationRules/Index- Escalation rules listEscalated/Admin/EscalationRules/Create- New escalation ruleEscalated/Admin/EscalationRules/Edit- Edit escalation ruleEscalated/Admin/CannedResponses/Index- Canned responsesEscalated/Admin/Macros/Index- Macros managementEscalated/Admin/Reports- Reports dashboardEscalated/Admin/Settings- Settings management
Escalated/Guest/Create- Guest ticket formEscalated/Guest/Show- Guest ticket view
All routes are named with the escalated. prefix:
# Customer
escalated.customer.tickets.index
escalated.customer.tickets.create
escalated.customer.tickets.store
escalated.customer.tickets.show
escalated.customer.tickets.reply
escalated.customer.tickets.close
escalated.customer.tickets.reopen
escalated.customer.tickets.rate
# Agent
escalated.agent.dashboard
escalated.agent.tickets.index
escalated.agent.tickets.bulk
escalated.agent.tickets.show
escalated.agent.tickets.update
escalated.agent.tickets.reply
escalated.agent.tickets.note
escalated.agent.tickets.assign
escalated.agent.tickets.status
escalated.agent.tickets.priority
escalated.agent.tickets.tags
escalated.agent.tickets.department
escalated.agent.tickets.macro
escalated.agent.tickets.follow
escalated.agent.tickets.presence
escalated.agent.tickets.pin
# Admin
escalated.admin.reports
escalated.admin.tickets.index
escalated.admin.tickets.bulk
escalated.admin.tickets.show
escalated.admin.tickets.reply
escalated.admin.tickets.note
escalated.admin.tickets.assign
escalated.admin.tickets.status
escalated.admin.tickets.priority
escalated.admin.tickets.tags
escalated.admin.tickets.department
escalated.admin.tickets.macro
escalated.admin.tickets.follow
escalated.admin.tickets.presence
escalated.admin.tickets.pin
escalated.admin.settings
escalated.admin.settings.update
escalated.admin.departments.index
escalated.admin.departments.create
escalated.admin.departments.store
escalated.admin.departments.edit
escalated.admin.departments.update
escalated.admin.departments.destroy
escalated.admin.sla-policies.index
escalated.admin.sla-policies.create
escalated.admin.sla-policies.store
escalated.admin.sla-policies.edit
escalated.admin.sla-policies.update
escalated.admin.sla-policies.destroy
escalated.admin.escalation-rules.index
escalated.admin.escalation-rules.create
escalated.admin.escalation-rules.store
escalated.admin.escalation-rules.edit
escalated.admin.escalation-rules.update
escalated.admin.escalation-rules.destroy
escalated.admin.tags.index
escalated.admin.tags.store
escalated.admin.tags.update
escalated.admin.tags.destroy
escalated.admin.canned-responses.index
escalated.admin.canned-responses.store
escalated.admin.canned-responses.update
escalated.admin.canned-responses.destroy
escalated.admin.macros.index
escalated.admin.macros.store
escalated.admin.macros.update
escalated.admin.macros.destroy
# Guest (no auth)
escalated.guest.tickets.create
escalated.guest.tickets.store
escalated.guest.tickets.show
escalated.guest.tickets.reply
escalated.guest.tickets.rate
# Inbound Email (no auth)
escalated.inbound.webhook
Tickets follow a strict state machine:
open -> in_progress, waiting_on_customer, waiting_on_agent, escalated, resolved, closed
in_progress -> waiting_on_customer, waiting_on_agent, escalated, resolved, closed
waiting_on_customer -> open, in_progress, resolved, closed
waiting_on_agent -> open, in_progress, escalated, resolved, closed
escalated -> in_progress, resolved, closed
resolved -> reopened, closed
closed -> reopened
reopened -> in_progress, waiting_on_customer, waiting_on_agent, escalated, resolved, closed
All tables use the escalated_ prefix by default (configurable):
escalated_departments+escalated_department_agentpivotescalated_sla_policiesescalated_ticketsescalated_repliesescalated_attachmentsescalated_tags+escalated_ticket_tagpivotescalated_ticket_activitiesescalated_escalation_rulesescalated_canned_responsesescalated_settingsescalated_inbound_emailsescalated_macrosescalated_ticket_followersescalated_satisfaction_ratings
The provider automatically shares the following data via Inertia on every request:
{
escalated: {
prefix: 'support', // Route prefix
is_agent: false, // Whether current user is an agent
is_admin: false, // Whether current user is an admin
guest_tickets_enabled: true, // Whether guest tickets are enabled
}
}The package emits the following events that you can listen to:
| Event | Description |
|---|---|
escalated:ticket:created |
New ticket created |
escalated:ticket:updated |
Ticket fields updated |
escalated:ticket:status_changed |
Status transition |
escalated:ticket:assigned |
Agent assigned |
escalated:ticket:unassigned |
Agent unassigned |
escalated:ticket:priority_changed |
Priority changed |
escalated:ticket:department_changed |
Department changed |
escalated:ticket:tags_added |
Tags added |
escalated:ticket:tags_removed |
Tags removed |
escalated:ticket:escalated |
Ticket escalated |
escalated:ticket:resolved |
Ticket resolved |
escalated:ticket:closed |
Ticket closed |
escalated:ticket:reopened |
Ticket reopened |
escalated:reply:created |
Reply added |
escalated:note:created |
Internal note added |
escalated:sla:breached |
SLA target breached |
escalated:rating:created |
CSAT rating submitted |
Enable inbound email processing by setting inboundEmail.enabled: true in your config. Point your email provider's webhook to:
POST /support/inbound/mailgun
POST /support/inbound/postmark
POST /support/inbound/ses
The system will:
- Parse incoming email from the adapter's format
- Match to existing tickets via subject pattern (
[ESC-00001]) or In-Reply-To/References headers - Create a reply on existing tickets or create a new ticket
- Process attachments (with blocked extension filtering)
- Log the inbound email for audit trail
You can resolve services from the container for custom logic:
import app from '@adonisjs/core/services/app'
const ticketService = await app.container.make('escalated.ticketService')
const ticket = await ticketService.create({
subject: 'Help needed',
description: 'I have a question...',
priority: 'medium',
channel: 'api',
}, { id: 1, type: 'User' }) // requester
const assignmentService = await app.container.make('escalated.assignmentService')
await assignmentService.autoAssign(ticket)This package serves only the backend API via Inertia.js. The shared Vue 3 frontend components are provided by the @escalated-dev/escalated package, which is framework-agnostic and works with all Escalated backends (AdonisJS, Laravel, Rails, Django).
npm install @escalated-dev/escalated- Escalated for Laravel — Laravel Composer package
- Escalated for Rails — Ruby on Rails engine
- Escalated for Django — Django reusable app
- Escalated for AdonisJS — AdonisJS v6 package (you are here)
- Escalated for Filament — Filament v3 admin panel plugin
- Shared Frontend — Vue 3 + Inertia.js UI components
Same architecture, same Vue UI, same three hosting modes — for every major backend framework.
MIT