Index / Work
TickIt — Configurable ticketing & helpdesk
Internal tooling & integrations

TickItConfigurable ticketing & helpdesk

TickIt is a configurable ticketing and helpdesk platform — a Laravel 13 API behind a Vue 3 single-page app. Admins build their own ticket types, each with its own statuses and custom fields, without touching code; a versioned integration API lets other apps file tickets programmatically with idempotent, replay-safe intake. Client, agent, and admin roles each get their own views, with a Kanban board, threaded comments, file attachments, and an activity log.

TickIt is a place for apps to send their feedback and for teams to work it. The interesting part isn’t the ticket list — it’s that the workflow is data, not code. A ticket type carries its own statuses and its own custom fields, so an admin can stand up a new kind of ticket, with its own pipeline and its own form, without anyone writing a migration. TicketWorkflowService reads that configuration to decide which moves are legal.

The other half is the integration API. Household and Chrono don’t email their bug reports — they POST them. That endpoint is versioned and authenticated per client, and it’s idempotent: ApiClientTicketService.createOrReplay() keys each submission on an Idempotency-Key, replays the original ticket on retry, and leans on a unique database constraint to settle the race when two retries land at once. Around that sit the things you’d expect of a helpdesk — a Kanban board, Tiptap-authored comments, file attachments, an activity log, and web-push plus mail notifications when work arrives.

Around the workflow engine and the integration API sit the everyday parts of a helpdesk — projects, ticket assignment, comments, attachments and notifications — so the configurable core and the machine-to-machine intake both land in a tool people actually run day to day.

RoleDesign + full-stack
Year2026
Core stackLaravel 13 · Vue 3 · TypeScript
AuthSanctum + Google OAuth
Consumed byHousehold & Chrono (integration API)
TickIt Kanban board view at /tickets/board — tickets as cards in per-status columns, with type and assignee badges, dragging across a configurable workflow
Screenshot soon
Workflow engine

Ticket types that configure themselves

Each ticket type owns its own set of statuses and its own custom fields. Admins define the type, its workflow states, and the fields agents fill in — none of it hardcoded. TicketWorkflowService governs which status transitions are legal.

  • Per-type statuses, not one global set
  • Custom fields with type, options, required, and ordering
  • Transitions validated server-side
Screenshot soon
Dynamic fields

Custom fields without a migration

TicketTypeField stores a label, field type, a JSON options blob for select-style fields, a required flag, and a sort order — so a new field is a row, not a schema change. The admin UI reorders fields by drag.

  • JSON options for choice fields
  • is_required + sort_order per field
  • Reorderable in the admin view
Screenshot soon
Integration API

Other apps file tickets programmatically

A versioned endpoint (Integrations/V1/TicketController) lets external apps submit feedback tickets. Household and Chrono both use it — authenticated per-client by AuthenticateApiClient middleware against an ApiClient token, scoped to a project.

  • Per-client API tokens
  • Project-scoped intake
  • Versioned (V1) surface
Screenshot soon
Kanban & collaboration

A board, comments, and attachments

Tickets live on a drag-and-drop board at /tickets/board organised by the type's statuses. Each ticket carries threaded comments authored in a Tiptap editor, uploadable and downloadable file attachments, and a full activity log.

  • Status columns from the workflow
  • Tiptap comment composer
  • File upload / download + activity trail
Screenshot soon
Notifications

Push and mail on the events that matter

When a ticket is created, TickIt fans out a TicketCreated notification over web push (VAPID keys, push_subscriptions table) and the mail channel, so agents hear about new work without watching the board.

  • Web push via service-worker subscription
  • Mail channel alongside push
  • Driven by TicketNotifier
Screenshot soon
Roles & onboarding

Three audiences, three views

Client, agent, and admin each get a dedicated view tree. New projects spin up from templates via ProjectSetupService, so a fresh project arrives with sensible default types, statuses, and fields rather than a blank slate.

  • Separate client / agent / admin UIs
  • Template-driven project setup
  • Sanctum + Google OAuth sign-in
Architecture

A static Vue SPA talks to a stateless Laravel API; a second, versioned API surface accepts machine-to-machine ticket intake from sibling apps.

Client
Vue 3 SPA
TypeScript, TanStack Query/Table, Pinia, shadcn-vue, Tiptap; role-scoped client/agent/admin views, served as a PWA.
API
Laravel 13 + Sanctum
48 routes over 10 models; Google OAuth for humans, per-client tokens for machines. TicketWorkflowService enforces transitions.
Integration
Versioned V1 endpoint
AuthenticateApiClient middleware; ApiClientTicketService handles idempotent createOrReplay intake from Household and Chrono.
Async
Notifications & jobs
TicketCreated over web push (VAPID) and mail; queued work via the Laravel queue worker.
Challenges solved
01

Configurable without code

Problem

Different teams want different ticket shapes — distinct statuses, distinct fields — and shipping a migration per request doesn't scale.

Solution

Modelled the workflow as data. TicketType owns its TicketStatus rows and TicketTypeField rows (label, field_type, JSON options, is_required, sort_order). Admins compose types in the UI; TicketWorkflowService reads that data to validate transitions, so new workflows need no deploy.

02

Idempotent machine intake

Problem

External apps retry on network failure. A naive create-ticket endpoint would duplicate a ticket every time a client resent the same request.

Solution

createOrReplay() keys each submission on an Idempotency-Key. A repeat key returns the original ticket instead of creating a second one — making retries safe for the calling app.

03

Racing duplicate requests

Problem

Two retries can arrive close enough that both pass the initial "does this key exist?" lookup before either has committed.

Solution

A unique constraint on idempotency_key is the source of truth. The service catches the unique-constraint violation on insert, then re-reads and returns the winning ticket — so the database, not application timing, resolves the race.

04

One app, three audiences

Problem

Clients, agents, and admins need very different surfaces over the same data without leaking each other's capabilities.

Solution

Separate view trees (client / agent / admin) on the frontend, backed by Sanctum-authenticated, role-aware API access. Google OAuth covers human sign-in; the integration API stays on its own per-client token path entirely.

How it's built
Backend
PHP 8.3, Laravel 13, Sanctum, Google OAuth, MariaDB
Integrations & notifications
Versioned integration API (V1), Idempotency-Key replay, Web push (VAPID), Mail channel
Frontend
Vue 3, TypeScript, TanStack Query, TanStack Table, Pinia, shadcn-vue, Tailwind
Editing & UX
Tiptap WYSIWYG, Sonner toasts, Vite, PWA

A real product, built honestly — configurable where it counts, safe where machines talk to it.