Embeddable support ticket system for Django
Project description
Escalated for Django
A full-featured, embeddable support ticket system for Django. Drop it into any app — get a complete helpdesk with SLA tracking, escalation rules, agent workflows, and a customer portal. No external services required.
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
- 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
- Inertia.js + Vue 3 UI — Shared frontend via
@escalated-dev/escalated
v0.4.0 — Advanced Features
- Bulk actions — Assign, change status/priority, add tags, close, or delete multiple tickets at once
- Macros — Reusable multi-step automations (set status + assign + add note in one click)
- Ticket followers — Agents follow tickets and receive the same notifications as the assignee
- Satisfaction ratings — 1-5 star CSAT ratings with optional comments after resolution
- Pinned notes — Pin important internal notes to the top of the ticket thread
- Keyboard shortcuts — Full keyboard navigation for power users
- Quick filters — One-click filter chips (My Tickets, Unassigned, Urgent, SLA Breaching)
- Presence indicators — See who else is viewing a ticket in real-time
- Enhanced dashboard — CSAT metrics, resolution times, SLA breach tracking
Requirements
- Python 3.10+
- Django 4.2+
- Node.js 18+ (for frontend assets)
Quick Start
pip install escalated-django
npm install @escalated-dev/escalated
1. Add to INSTALLED_APPS
INSTALLED_APPS = [
# ...
'django.contrib.contenttypes',
'inertia',
'escalated',
]
2. Include URLs
from django.urls import path, include
urlpatterns = [
# ...
path("support/", include("escalated.urls")),
]
3. Run migrations
python manage.py migrate escalated
Visit /support — you're live.
Frontend Setup
Escalated uses Inertia.js with Vue 3. The frontend components are provided by the @escalated-dev/escalated npm package.
Tailwind Content
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',
],
Page Resolver
Add the Escalated pages to your Inertia page resolver:
// frontend/main.js
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp({
resolve: name => {
if (name.startsWith('Escalated/')) {
const escalatedPages = import.meta.glob(
'../node_modules/@escalated-dev/escalated/src/pages/**/*.vue',
{ eager: true }
)
const pageName = name.replace('Escalated/', '')
return escalatedPages[`../node_modules/@escalated-dev/escalated/src/pages/${pageName}.vue`]
}
const pages = import.meta.glob('./pages/**/*.vue', { eager: true })
return pages[`./pages/${name}.vue`]
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
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 BaseLayout from '@/layouts/BaseLayout.vue'
createInertiaApp({
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.use(EscalatedPlugin, {
layout: BaseLayout,
theme: {
primary: '#3b82f6',
radius: '0.75rem',
}
})
.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.
See the @escalated-dev/escalated README for full theming documentation and CSS custom properties.
Hosting Modes
Self-Hosted (default)
Everything stays in your database. No external calls. Full autonomy.
ESCALATED = {
"MODE": "self_hosted",
}
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.
ESCALATED = {
"MODE": "synced",
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
"HOSTED_API_KEY": "your-api-key",
}
Cloud
All ticket data proxied to the cloud API. Your app handles auth and renders UI, but storage lives in the cloud.
ESCALATED = {
"MODE": "cloud",
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
"HOSTED_API_KEY": "your-api-key",
}
All three modes share the same views, UI, and business logic. The driver pattern handles the rest.
Configuration
Add to your settings.py:
ESCALATED = {
"MODE": "self_hosted", # self_hosted | synced | cloud
"TABLE_PREFIX": "escalated_",
"ROUTE_PREFIX": "support",
"DEFAULT_PRIORITY": "medium",
# Tickets
"ALLOW_CUSTOMER_CLOSE": True,
"AUTO_CLOSE_RESOLVED_AFTER_DAYS": 7,
"MAX_ATTACHMENTS": 5,
"MAX_ATTACHMENT_SIZE_KB": 10240,
# SLA
"SLA": {
"ENABLED": True,
"BUSINESS_HOURS_ONLY": False,
"BUSINESS_HOURS": {
"START": "09:00",
"END": "17:00",
"TIMEZONE": "UTC",
"DAYS": [1, 2, 3, 4, 5],
},
},
# Notifications
"NOTIFICATION_CHANNELS": ["email"],
"WEBHOOK_URL": None,
# Cloud/Synced mode
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
"HOSTED_API_KEY": None,
}
Management Commands
# Check SLA deadlines and fire breach notifications
python manage.py check_sla
# Evaluate escalation rules against open tickets
python manage.py evaluate_escalations
# Auto-close tickets resolved more than N days ago
python manage.py close_resolved --days 7
# Purge old activity logs
python manage.py purge_activities --days 90
Schedule these with cron, Celery Beat, or django-crontab for automated enforcement.
Routes
All routes use the configurable prefix (default: support).
| Route | Method | Description |
|---|---|---|
/support/tickets/ |
GET | Customer ticket list |
/support/tickets/create/ |
GET | New ticket form |
/support/tickets/<id>/ |
GET | Ticket detail |
/support/agent/ |
GET | Agent dashboard |
/support/agent/tickets/ |
GET | Agent ticket queue |
/support/agent/tickets/<id>/ |
GET | Agent ticket view |
/support/admin/reports/ |
GET | Admin reports |
/support/admin/departments/ |
GET | Department management |
/support/admin/sla-policies/ |
GET | SLA policy management |
/support/admin/escalation-rules/ |
GET | Escalation rule management |
/support/admin/tags/ |
GET | Tag management |
/support/admin/canned-responses/ |
GET | Canned response management |
/support/agent/tickets/bulk/ |
POST | Bulk actions on multiple tickets |
/support/agent/tickets/<id>/follow/ |
POST | Follow/unfollow a ticket |
/support/agent/tickets/<id>/macro/ |
POST | Apply a macro to a ticket |
/support/agent/tickets/<id>/presence/ |
POST | Update presence on a ticket |
/support/agent/tickets/<id>/pin/<reply_id>/ |
POST | Pin/unpin an internal note |
/support/tickets/<id>/rate/ |
POST | Submit satisfaction rating |
Signals
Connect to ticket lifecycle events:
from escalated.signals import ticket_created, ticket_resolved
@receiver(ticket_created)
def on_ticket_created(sender, ticket, user, **kwargs):
print(f"New ticket: {ticket.reference}")
@receiver(ticket_resolved)
def on_ticket_resolved(sender, ticket, user, **kwargs):
print(f"Resolved: {ticket.reference}")
Available signals: ticket_created, ticket_updated, ticket_status_changed, ticket_assigned, ticket_unassigned, ticket_priority_changed, ticket_escalated, ticket_resolved, ticket_closed, ticket_reopened, reply_created, internal_note_added, sla_breached, sla_warning, tag_added, tag_removed, department_changed.
Also Available For
- Escalated for Laravel — Laravel Composer package
- Escalated for Rails — Ruby on Rails engine
- Escalated for Django — Django reusable app (you are here)
- Escalated for AdonisJS — AdonisJS v6 package
- 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.
Development
pip install -e ".[dev]"
pytest
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file escalated_django-0.4.0.tar.gz.
File metadata
- Download URL: escalated_django-0.4.0.tar.gz
- Upload date:
- Size: 76.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da9aa01c45d6b5a078dfbfaefd5161f154b060ddba63ce8dbdc67483318ec777
|
|
| MD5 |
d2f231039ec67befa57697332af8fde3
|
|
| BLAKE2b-256 |
260c0aafec71a6ddfba7612bf8ee37a48ebd824b99903452ca850a9082db1ea9
|
File details
Details for the file escalated_django-0.4.0-py3-none-any.whl.
File metadata
- Download URL: escalated_django-0.4.0-py3-none-any.whl
- Upload date:
- Size: 99.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0ad0cfeb73b9edb2101ea9a2fd34381c9cb8959777768c91868a40f8da957d3
|
|
| MD5 |
a72055fb2c2edfcc7b93ad385f66fbfb
|
|
| BLAKE2b-256 |
da045219743e2a1f91c9b713d5878f6cd8a498aac59978eb617d47a923fe67ec
|