Skip to content

Repository files navigation

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

Escalated Spring

An embeddable helpdesk system for Spring Boot applications. Add a full-featured support desk to any Java application with a single dependency.

Features

  1. Ticket CRUD -- Full lifecycle management with statuses, priorities, and assignments
  2. SLA Policies -- Configurable SLAs with business hours support and holiday calendars
  3. Automations -- Time-based rules for auto-closing resolved tickets and auto-assignment
  4. Escalation Rules -- Automatic escalation on SLA breach with reassignment and notifications
  5. Macros & Canned Responses -- Pre-defined actions and response templates for agents
  6. Custom Fields -- Extensible ticket data with multiple field types
  7. Knowledge Base -- Articles and categories with search, view counts, and feedback
  8. Webhooks -- HMAC-signed webhook delivery with retry logic
  9. API Tokens -- SHA-256 hashed token authentication for API access
  10. Roles & Permissions -- Granular role-based access control
  11. Audit Logging -- Complete audit trail for all actions
  12. Import System -- Bulk ticket import from structured data
  13. Side Conversations -- Private threaded conversations within tickets
  14. Ticket Merging & Linking -- Merge duplicate tickets and link related ones
  15. Ticket Splitting -- Split complex tickets into separate issues
  16. Ticket Snooze -- Snooze tickets with automatic wake-up via @Scheduled
  17. Email Threading -- Branded HTML email templates via Thymeleaf with proper Message-ID threading
  18. Saved Views -- Custom filtered/sorted ticket views per agent
  19. Widget API -- Public REST endpoints for embedding a support widget
  20. Real-time Broadcasting -- WebSocket via STOMP/SockJS (opt-in)
  21. Capacity Management -- Track and enforce agent workload limits
  22. Skill-based Routing -- Route tickets to agents with matching skills
  23. CSAT Ratings -- Customer satisfaction surveys with token-based access
  24. 2FA (TOTP) -- Time-based one-time password support for agent accounts
  25. Guest Access -- Token-based ticket access without authentication
  26. Inbound Email -- Single webhook endpoint with Postmark + Mailgun + AWS SES parsers, signed Reply-To verification, and Message-ID-based ticket resolution

Requirements

  • Java 17+
  • Spring Boot 3.2+
  • A relational database (PostgreSQL, MySQL, or H2 for development)

Installation

Add the dependency to your build.gradle.kts:

implementation("dev.escalated:escalated-spring:0.1.0")

Or pom.xml:

<dependency>
<groupId>dev.escalated</groupId>
<artifactId>escalated-spring</artifactId>
<version>0.1.0</version>
</dependency>

Configuration

Add to your application.properties or application.yml:

# Enable/disable the helpdeskescalated.enabled=true
# Route prefix (default: escalated)escalated.route-prefix=escalated
# Feature togglesescalated.knowledge-base.enabled=true
escalated.broadcasting.enabled=false
escalated.two-factor.enabled=true
escalated.widget.enabled=true
escalated.guest-access.enabled=true
# SLA checking intervalescalated.sla.check-interval-seconds=60
# Snooze wake-up intervalescalated.snooze.check-interval-seconds=60
# Webhook settingsescalated.webhook.max-retries=3
# Inbound email (symmetric secret used for signed Reply-To + webhook verification)escalated.mail.domain=support.yourapp.com
escalated.mail.inbound-secret=${ESCALATED_INBOUND_SECRET}
# Database (example for PostgreSQL)spring.datasource.url=jdbc:postgresql://localhost:5432/myapp
spring.datasource.username=user
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=validate
spring.flyway.enabled=true

Ticket subjects

A ticket has a requester (who raised it) and a subject line (free text). Tickets can also be about host-app entities — a Project, Customer, asset — that are not people. Attach them as ticket subjects so agents see what the ticket concerns and can jump to the entity in your app.

Implement the dev.escalated.contracts.TicketSubject interface on host models and register a TicketSubjectResolver bean to resolve type/id pairs for the UI:

@ComponentpublicclassProjectTicketSubjectResolverimplementsTicketSubjectResolver {
privatefinalProjectRepositoryprojects;
@OverridepublicTicketSubjectresolve(StringsubjectType, StringsubjectId) {
if (!"com.example.Project".equals(subjectType)) {
returnnull;
}
returnprojects.findById(subjectId).orElse(null);
}
}

Allow types the admin API may attach (prevents arbitrary type injection):

escalated:
ticket-subjects:
types:
- com.example.Project
- com.example.Customer

Attach or detach via the admin API:

POST /escalated/api/admin/tickets/{ticketId}/subjects { "type", "id", "role"? }
DELETE /escalated/api/admin/tickets/{ticketId}/subjects/{linkId}

Ticket detail responses include subjects[] with { type, id, role, title, subtitle, url, color, icon, missing }. When no resolver is registered or the entity is gone, title falls back to type#id and missing is true.

Programmatic attach via TicketSubjectService works for any type when the allowlist is empty; the API only accepts allowlisted types.

Custom Ticket Actions

Host applications can add custom buttons to the agent ticket screen and handle clicks with a Spring @EventListener. Register actions in application.yml:

escalated:
ticket-actions:
- key: sync-crmlabel: Sync CRMvariant: primary # primary | secondary | dangerconfirmation: "Sync this ticket to the CRM?"metadata:
icon: refresh-cw

Visible actions are exposed on the agent ticket detail response as custom_actions (each with a url and method). Triggering one (POST /escalated/api/agent/tickets/{id}/actions/{action}) validates the action is visible (404) and enabled (403), records an internal note for auditability, and publishes a CustomActionTriggeredEvent:

@ComponentpublicclassCrmSyncListener {
@EventListenerpublicvoidonCustomAction(CustomActionTriggeredEventevent) {
if (!"sync-crm".equals(event.getAction())) {
return;
}
// event.getTicket(), event.getUserEmail(), event.getPayload(), event.getMetadata()
}
}

Database Setup

Flyway migrations are included and run automatically. The migration creates all tables prefixed with escalated_ and seeds default roles and permissions.

Internationalization

Translations are consumed from the central dev.escalated:escalated-locale Maven artifact, which ships bundles at META-INF/escalated/locale/messages_{locale}.properties on the classpath. The auto-configured MessageSource chains two basenames so host apps can override keys without forking the central bundle:

  1. classpath:i18n/overrides/messages — sparse host-app overrides (first match wins)
  2. classpath:META-INF/escalated/locale/messages — central artifact (canonical strings)

Drop a messages_{locale}.properties file under src/main/resources/i18n/overrides/ to override individual keys. See src/main/resources/i18n/overrides/README.md for examples. To fix a typo or mistranslation that affects every host plugin, open a PR against the central escalated-locale repo instead.

Inbound email

Point your Postmark, Mailgun, or AWS SES (via SNS HTTP subscription) inbound webhook at:

POST /escalated/webhook/email/inbound?adapter=postmark
POST /escalated/webhook/email/inbound?adapter=mailgun
POST /escalated/webhook/email/inbound?adapter=ses

The adapter can be selected via the query parameter or the X-Escalated-Adapter header. Your provider must attach the shared secret as an X-Escalated-Inbound-Secret header, which is compared with MessageDigest.isEqual (timing-safe).

The service resolves inbound messages to existing tickets via, in order: canonical Message-ID headers, signed Reply-To verification, and subject-reference tags. Unmatched messages with real content create a new ticket; SNS subscription confirmations and empty body+subject messages are skipped.

See the inbound email docs for provider setup, the response shape, and a ready-to-paste curl test recipe.

API Endpoints

Admin (/escalated/api/admin/)

MethodPathDescription
GET/ticketsList tickets (paginated, filterable)
POST/ticketsCreate ticket
GET/tickets/{id}Get ticket
PUT/tickets/{id}Update ticket
POST/tickets/{id}/assignAssign ticket
POST/tickets/{id}/statusChange status
POST/tickets/{id}/snoozeSnooze ticket
POST/tickets/{id}/mergeMerge tickets
POST/tickets/{id}/splitSplit ticket
DELETE/tickets/{id}Delete ticket
GET/POST/departmentsCRUD departments
GET/POST/agentsCRUD agents
GET/POST/webhooksCRUD webhooks
GET/POST/rolesCRUD roles
GET/POST/custom-fieldsCRUD custom fields
GET/POST/settingsManage settings
GET/PUT/settings/public-ticketsRuntime guest-policy mode (unassigned / guest_user / prompt_signup). See docs.escalated.dev/public-tickets.
GET/audit-logsView audit logs
POST/import/ticketsImport tickets
GET/POST/kb/categoriesManage KB categories
GET/POST/kb/articlesManage KB articles

Agent (/escalated/api/agent/)

MethodPathDescription
GET/ticketsList assigned/filtered tickets
GET/tickets/{id}View ticket
POST/tickets/{id}/repliesAdd reply
POST/tickets/{id}/macro/{macroId}Apply macro
POST/tickets/{id}/side-conversationsCreate side conversation
POST/tickets/{id}/linksLink tickets
GET/POST/saved-viewsManage saved views
GET/POST/canned-responsesManage canned responses

Customer (/escalated/api/customer/)

MethodPathDescription
GET/tickets?email=List customer tickets
POST/ticketsCreate ticket
POST/tickets/{id}/repliesAdd reply

Widget (/escalated/api/widget/)

MethodPathDescription
POST/ticketsCreate ticket (public)
GET/tickets/{token}View ticket by guest token
POST/tickets/{token}/repliesReply via guest token
GET/kb/search?query=Search knowledge base
POST/csat/{token}Submit satisfaction rating

Guest (/escalated/api/guest/)

MethodPathDescription
GET/tickets/{token}View ticket
GET/tickets/{token}/repliesView replies
POST/tickets/{token}/repliesAdd reply

Architecture

dev.escalated/
config/ Auto-configuration, properties, WebSocket config
models/ JPA entities with full relationships
repositories/ Spring Data JPA repositories
services/ Business logic (transactional)
controllers/
admin/ Admin REST API
agent/ Agent REST API
customer/ Customer REST API
widget/ Public widget API
events/ Spring application events + webhook listener
security/ API token auth filter, security config, 2FA
scheduling/ @Scheduled tasks (snooze, SLA, automations)

Authentication

API endpoints use Bearer token authentication. Create tokens via the admin API:

curl -X POST /escalated/api/admin/tokens \
-H "Content-Type: application/json" \
-d '{"name": "My API Token", "agent_id": 1}'

The response includes the plain-text token (shown only once). Use it in subsequent requests:

curl -H "Authorization: Bearer <token>" /escalated/api/agent/tickets

WebSocket (Real-time)

Enable with escalated.broadcasting.enabled=true. Connect to /escalated/ws via SockJS/STOMP.

Development

# Build
./gradlew build
# Run tests
./gradlew test# Run checkstyle
./gradlew checkstyleMain checkstyleTest

Newsletters (optional, partial port)

JPA entities + renderer for the admin-only newsletter broadcast feature. Schema is auto-derived by Hibernate from the new @Entity classes when spring.jpa.hibernate.ddl-auto=update. Production hosts using Flyway / Liquibase generate the SQL migration with mvn spring-boot:run or mvn flyway:migrate after referencing this package.

importdev.escalated.services.newsletter.NewsletterRenderer;
varopts = newNewsletterRenderer.Options();
opts.baseUrl = "https://support.example.com";
opts.defaultTheme = "default";
opts.trackingEnabled = true;
opts.themesDir = "src/main/resources/templates/escalated/newsletter_themes";
opts.markdownToHtml = md -> /* plug in flexmark, commonmark-java, etc. */;
opts.brandName = "Acme";
opts.brandAccent = "#2563eb";
varrenderer = newNewsletterRenderer(opts);
varhtml = renderer.render(delivery, newsletter, contact, template);

Ships: models/newsletter/*.java (5 JPA entities), models/Contact.java (gains marketingOptOutAt), services/newsletter/NewsletterRenderer.java, resources/templates/escalated/newsletter_themes/{default,branded}.html.

Follow-up PR: Flyway / Liquibase migration files, planner/dispatcher/tracker services using Spring JpaRepositorys, Spring MVC controllers.

License

MIT License. See LICENSE for details.

About

Escalated support ticket system for Spring Boot

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages