Skip to content

escalated-dev/escalated-django

Repository files navigation

العربيةDeutschEnglishEspañolFrançaisItaliano日本語한국어NederlandsPolskiPortuguês (BR)РусскийTürkçe简体中文

Escalated for Django

Tests Python Django License: MIT

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.

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

  • 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
  • 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); python manage.py wake_snoozed_tickets management command auto-wakes them on schedule
  • Saved views / custom queues — Save, name, and share filter presets as reusable ticket views
  • Embeddable support widget — Lightweight <script> widget 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 Django Channels with automatic polling fallback
  • Knowledge base toggle — Enable or disable the public knowledge base from admin settings

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.

Plugin SDK

Escalated supports framework-agnostic plugins built with the Plugin SDK. Plugins are written once in TypeScript and work across all Escalated backends.

Requirements

  • Node.js 20+
  • @escalated-dev/plugin-runtime installed in your project

Installing Plugins

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

Enabling SDK Plugins

# settings.py
ESCALATED = {
    # ... existing config ...
    "SDK_ENABLED": True,
}

How It Works

SDK plugins run as a long-lived Node.js subprocess managed by @escalated-dev/plugin-runtime, communicating with Django over JSON-RPC 2.0 via stdio. Every ticket lifecycle signal is dual-dispatched — first to Django signal handlers, then forwarded to the plugin runtime.

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

See the detailed SDK Plugin Bridge section below for the full architecture, supported ctx.* callbacks, hook event mapping, and resilience documentation.

SDK Plugin Bridge

The Plugin Bridge connects your Django app to the Node.js @escalated-dev/plugin-runtime process via JSON-RPC 2.0 over stdio. It enables SDK plugins — JavaScript/TypeScript packages that hook into ticket lifecycle events, expose custom API endpoints, and persist data through the host ORM — without requiring any Node.js code in your Django project.

How it works

  1. On startup Django spawns node @escalated-dev/plugin-runtime as a long-lived subprocess.
  2. A protocol handshake is performed and plugin manifests are exchanged.
  3. URL patterns for plugin pages, API endpoints, and webhooks are dynamically registered.
  4. Every ticket lifecycle signal (created, replied, resolved, etc.) is dual-dispatched — first to the standard Django signal handlers and then to the bridge, which forwards the event to the runtime.
  5. Plugin code can call back into Django via ctx.* methods (ctx.tickets.find, ctx.store.set, ctx.config.get, etc.) over the same bidirectional JSON-RPC channel.

Requirements

  • Node.js 18+
  • @escalated-dev/plugin-runtime installed in your project's node_modules

Quick start

1. Install the runtime

npm install @escalated-dev/plugin-runtime

2. Enable the bridge in settings

ESCALATED = {
    # ... existing config ...

    # SDK plugin bridge
    "SDK_ENABLED": True,

    # Optional overrides (defaults shown):
    # "RUNTIME_COMMAND": "node node_modules/@escalated-dev/plugin-runtime/dist/index.js",
    # "RUNTIME_CWD": BASE_DIR,  # working directory for the Node subprocess
}

3. Run the migration

python manage.py migrate escalated

This creates the escalated_plugin_store table used by ctx.store.* and ctx.config.* callbacks.

Routes registered by the bridge

Plugin manifests can declare three types of routes. All are automatically registered under the configured ROUTE_PREFIX (default support):

Category URL pattern Auth
Pages /{prefix}/admin/plugins/{plugin}/{route} Admin required
Endpoints /{prefix}/api/plugins/{plugin}/{path} Admin required
Webhooks /{prefix}/webhooks/plugins/{plugin}/{path} None (public)

Supported ctx.* callbacks

Method Description
ctx.config.all / ctx.config.get / ctx.config.set Per-plugin config blob
ctx.store.get / ctx.store.set / ctx.store.query / ctx.store.insert / ctx.store.update / ctx.store.delete Per-plugin key/value store
ctx.tickets.find / ctx.tickets.query / ctx.tickets.create / ctx.tickets.update Ticket ORM access
ctx.replies.find / ctx.replies.query / ctx.replies.create Reply ORM access
ctx.contacts.find / ctx.contacts.findByEmail / ctx.contacts.create User model access
ctx.tags.all / ctx.tags.create Tag access
ctx.departments.all / ctx.departments.find Department access
ctx.agents.all / ctx.agents.find Agent (user) access
ctx.broadcast.toChannel / ctx.broadcast.toUser / ctx.broadcast.toTicket Django Channels broadcast (optional)
ctx.emit Fire another action hook from inside a plugin
ctx.log Log to Django's logger

Hook events dispatched to the bridge

Every ticket signal fires a corresponding SDK hook:

Django signal SDK hook
ticket_created ticket.created
ticket_updated ticket.updated
ticket_status_changed ticket.status_changed
ticket_assigned ticket.assigned
ticket_priority_changed ticket.priority_changed
ticket_resolved ticket.resolved
ticket_closed ticket.closed
ticket_escalated ticket.escalated
reply_created reply.created
sla_breached sla.breached

Resilience

  • The bridge is spawned lazily on first use — health-check requests are never slowed down.
  • If the Node.js runtime crashes it is automatically restarted with exponential backoff (up to 5 minutes between attempts).
  • Action hooks degrade gracefully (drop with a warning) when the runtime is unavailable. Filter hooks return the unmodified value.
  • The action queue is capped at 1 000 in-flight entries to prevent memory growth.

Also Available For

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

Development

pip install -e ".[dev]"
pytest

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages