Skip to main content

Tickets concept

This page explains how the ticket system works across the player shop, the admin queue, and Discord.

Use it when you need the shared mechanics behind ticket visibility, status changes, participants, and Discord behavior.

What a ticket containsโ€‹

Each ticket combines:

  • a subject
  • a status
  • a participant list
  • a message history
  • an optional assignee
  • an optional Discord channel
  • an optional request type ("category") and the answers the player gave when opening it

Shop-created tickets automatically add the creator as a participant. Staff-created tickets can start without participants and be filled later from the admin queue.

Staff-created and AI-created tickets have no request type: they never run an intake form, so there is nothing to record.

Ticket lifecycleโ€‹

Tickets move through these states:

  • Open: newly created or reopened
  • In progress: the first public reply turns an open ticket into in progress
  • Closed: the ticket is resolved but can still be reopened
  • Archived: the ticket is finished and the Discord channel is removed

Important behavior:

  • reopening a closed ticket keeps the existing Discord channel and unlocks it again
  • reopening an archived ticket creates a fresh Discord channel and replays the recent public history
  • if Tickets Auto Archive is enabled, closing a ticket archives it immediately instead of leaving it in the closed state

Participants and visibilityโ€‹

Participants define which players can see a ticket in Shop -> Tickets.

Staff can add:

  • single players
  • all current members of a faction

Important behavior:

  • only participants can see a ticket in the shop
  • internal notes are never shown to players
  • adding or removing a participant also updates Discord channel access for linked Discord accounts

Discord behaviorโ€‹

Navigation path for setup:

  • Server Admin -> Settings -> Discord

Relevant settings:

  • Discord Server
  • Discord Tickets Category โ€” only the fallback for a support level that has no category of its own
  • Discord Tickets Channel Prefix
  • Tickets Auto Archive

When Discord is configured and the bot is installed:

  • each ticket gets its own Discord text channel, created inside its support level's category
  • each support level owns a bot-managed Discord role whose members can read that level's channels
  • staff receive those roles automatically, driven by their level rights in dzbot
  • public messages are mirrored between dzbot and Discord
  • internal notes stay private in dzbot and only post a short notice into the Discord channel
  • archived tickets send participants a DM with reopen and view actions

Attention stateโ€‹

Each active ticket tracks who sent the last public message. This drives in-app badges shown on every ticket row in the admin queue and the player shop list:

  • Needs Attention โ€” the last public message is from a player; support has not yet replied
  • Waiting for Response โ€” the last public message is from support; the player has not yet replied

Discord channel header actionsโ€‹

The pinned header message in every ticket channel contains interactive controls:

  • Close / Reopen / Archive โ€” status buttons visible to anyone with the corresponding access right
  • Assign to me / Remove assignee โ€” assignment buttons
  • Add participant user-select โ€” a Discord user-select that lets moderators pick any Discord user to add as a participant directly from the channel; requires the admin.tickets.add_participant access right
  • Change support level button โ€” only shown on multi-level servers. Pressing it answers with a private select of the target levels; the level names deliberately never appear in the pinned message, which the ticket's participants can read. Staff without admin.tickets.change_level get a private refusal.

When a selected user has no linked player account, the bot replies with an error message in the channel. Re-selecting an existing participant is silently ignored.

Request types and the intake formโ€‹

Every server has a configurable set of request types (categories). Each type owns a small form the player fills in before the ticket exists, so a report arrives complete instead of as "hi I have a problem": a faction application asks for the faction name, a multi-account report asks who is being reported and what the evidence is.

The point is a clean intake, not analytics. Nothing in the system reads the structured answers back โ€” they exist so that staff can see exactly what was asked and exactly what came back.

Field typesโ€‹

A form has at most five fields. That is not a design preference: a Discord modal takes "between 1 and 5 components" and a modal submit cannot open a second modal, and both surfaces have to show the identical form โ€” so Discord is the floor.

TypeDiscordShop
Short textsingle-line text input<input>
Long textmulti-line text input<textarea>
Choicedropdown, radio group or checkbox groupselect, radios or checkboxes
Checkboxcheckbox (a required one must be ticked)checkbox
File uploadfile upload, 1โ€“10 filesfile input

A field also carries an optional hint, which is shown next to the input and costs no form slot.

How the answers become a ticketโ€‹

Each type has two templates, both localized:

  • the subject template, which produces the ticket subject
  • the message template, which produces the ticket's first message

Available placeholders:

PlaceholderSubjectMessageValue
{category}โœ…โœ…the request type's name in the language the form was filled in
{player}โœ…โœ…the creator's display name
{field:<key>}โœ…โœ…one field's answer (choice โ†’ the option labels, checkbox โ†’ yes/no, file โ†’ the filenames)
{fields}โŒโœ…one Label: value line per filled field, in form order

Rules that matter in practice:

  • a placeholder that resolves to nothing is replaced with nothing, and a line that ends up blank is dropped
  • the subject is collapsed to one line and trimmed to 100 characters; if it renders empty it falls back to the request type's name, and then to "Ticket"
  • the first message has the same chain, falling back to the request type's name and then to a dash โ€” a form whose fields are all optional and all left blank must not produce a blank opening post
  • templates are plain text; ticket messages are mirrored into Discord and rendered in the shop, and markdown would show its asterisks on one of the two

The answer snapshotโ€‹

Alongside the rendered message, the ticket stores a snapshot of the intake: the field labels and the selected option labels are copied into it. A ticket is a record, so renaming a field or deleting the request type three months later never rewrites or blanks an old ticket. Deleting a type leaves its tickets readable; they only lose their badge and their place in the queue filter.

The welcome messageโ€‹

A request type can carry a welcome message, posted as the opening staff message before the player's own first message, so it reads as a preamble. It is localized like everything else on the type and is resolved in the language the form was filled in.

This used to be one server-wide setting (Ticket Welcome Message). It moved onto the request type because what a faction applicant needs to be told is not what a multi-account reporter needs to be told. Two consequences:

  • the server setting no longer exists; each type is configured on its own, and an empty one posts nothing
  • a ticket without a request type gets no welcome message at all โ€” that means staff-created tickets (which never had one), AI-created tickets, and the legacy ticket_embed_create button until its server publishes real panels

Like the intake message it is plain text, and it is only posted when the ticket has a Discord channel.

Discord panelsโ€‹

A request type can own a panel message per Discord channel: an embed with the type's name and description, and one "open ticket" button per maintained language. The clicked button carries the language, so nothing has to be guessed from the player's Discord settings.

Several panels per type is the normal case, not an edge one: a guild that separates registered players from guests has more than one channel a ticket may be opened from, and the same type has to be reachable from each. The button carries only the type and the language, so every panel of a type is interchangeable โ€” a ticket opened from the guest channel is the same ticket as one opened from the members channel.

A language counts as maintained only when the type's name and every field label are filled in it โ€” otherwise a German player would face a half-English form.

The panel speaks the server's language: the embed title, the description leading it and the first button all use the language configured for the server. Every other maintained language follows behind it โ€” its description as a section headed in that language ("English version" on a German server), its button next in the row. A type maintained in a single language renders that one unlabelled, whichever it is.

Panels are managed: each one's channel and message id are stored and the message is edited in place whenever the type changes, because a stale panel whose button no longer matches its form is the classic ticket-bot failure. Three consequences:

  • publishing is refused until the type has at least one field and at least one maintained language
  • every edit fans out over all of the type's panels, and one channel where the bot lost permissions does not stop the others from being rewritten
  • Discord cannot reorder messages, so reordering request types does not move their panels; there is an explicit "Rebuild panels" action that deletes and reposts the panels of one channel

A panel row lives exactly as long as its Discord message: if someone deletes the message, the next edit attempt drops the row and the editor offers to publish it again.

Routingโ€‹

A request type can name a default support level, so the most common routing wish โ€” "this kind of request always belongs to team X" โ€” needs no manual escalation. Without one, tickets of that type start in the entry queue like everything else.

Support levelsโ€‹

Ticket handling can be split into support levels, so first-level staff work the entry queue only and escalated tickets become invisible to them.

  • Every server has at least one level. New tickets always enter at position 1.
  • Levels are configurable in count and name under Server Admin -> Tickets -> Levels.
  • Visibility is cumulative: the highest level a staff member holds wins, and every level below it stays visible. Forgetting a checkbox costs sight of the level above, never of the tickets below.
  • A ticket's level is authoritative in dzbot. Everything staff-facing respects it: the queue, the ticket detail page, global search, the AI companion's ticket tools, the player dossier, and the Discord channel buttons.
  • Players never see levels. A participant always reaches their own ticket, wherever staff moved it to.
  • Dashboards and statistics are deliberately not level-filtered โ€” those are system-wide numbers.

Escalating and de-escalatingโ€‹

A ticket can be moved to any higher level, and back down to any lower one, from the admin ticket detail page or from the pinned Discord channel header. Both require admin.tickets.change_level plus current sight of the ticket, and both demand a reason.

On every level change:

  • the reason is recorded as an internal note naming both levels and the acting staff member
  • the assignee is cleared, so the ticket surfaces as unhandled in the receiving queue
  • status and attention state stay untouched
  • the Discord channel moves into the new level's category and its role overwrites are swapped

Moving a ticket above your own level is allowed; the app warns you first and returns you to the queue afterwards, because the ticket is no longer yours to see.

Levels in Discordโ€‹

Each level owns:

  • a category, created by the bot when the level is created and swappable later from the management page
  • a bot-managed role whose membership follows the level right in dzbot โ€” dzbot rights are the single source of truth, exactly as for the connected and online roles

Because visibility is cumulative, a staff member holding level 3 also gets the roles of levels 1 and 2, so the lower categories stay readable.

If Discord is not installed or the bot lacks permissions, levels still work โ€” the category and role stay empty and the management drawer offers a repair action.

Locked ticket referencesโ€‹

A note, restriction or dossier entry may point at a ticket the viewer cannot see. Such a reference keeps its link and is marked with a lock, but its subject is withheld. Opening it shows which level the ticket sits in instead of a generic access error, so the surrounding context is never silently lost.

Access-right modelโ€‹

Player-facing rights:

  • player.tickets.view to open the shop ticket area
  • player.tickets.create to create new tickets
  • player.tickets.send_message to reply and edit the subject while the ticket is active
  • player.tickets.close to close active tickets
  • player.tickets.archive to archive resolved tickets when auto-archive is not active

Admin-facing rights:

  • admin.tickets.view for read-only access to the admin queue
  • admin.tickets.reply for public support replies, staff-created tickets, message pinning, and evidence file uploads
  • admin.tickets.internal_message for internal notes
  • admin.tickets.assign for assignee, participants, and subject management in the admin panel
  • admin.tickets.change_status for manual status changes
  • admin.tickets.add_participant to add participants via the Discord channel user-select
  • admin.tickets.change_level to move a ticket between support levels
  • admin.tickets.manage_levels to create, rename, reorder and delete support levels
  • admin.tickets.manage_categories to create, edit, reorder and delete request types and publish their Discord panels
  • admin.tickets.level_<id> โ€” one right per support level; see Support levels
  • logs.view to use the Investigation tab on the ticket detail page
  • restrictions.view to view linked restrictions on the ticket Review tab
  • restrictions.create to create restrictions directly from a ticket

For group strategy and mixed staff/player setups, see Access control.