WorkService Desk – Diagnostics-First Support Platform & In-App Vue Widget~9 min read
SaaS & Customer SupportActive ProductionProprietarySolo Engineer & Product Owner · Proficient Information Systems

Service Desk – Diagnostics-First Support Platform & In-App Vue Widget

Multi-tenant helpdesk unifying a native Vue in-app widget, IMAP email and public web forms into one agent inbox, shipped as reusable NPM packages for the company suite and as a standalone web app.

Role:Full-Stack Software Engineer (Solo Owner)
Timeline:2026 — Present
Vue 3Laravel 11MongoDBMySQLTypeScript+1

Planned, validated with the client, and delivered the Service Desk hub, reusable NPM packages, native Vue in-app widget and standalone web app as the only engineer, working from architecture direction set by the CEO and company architect.

My contribution

Solo Engineer & Product Owner · Proficient Information Systems

Planned, validated with the client, and delivered the Service Desk hub, reusable NPM packages, native Vue in-app widget and standalone web app as the only engineer, working from architecture direction set by the CEO and company architect.

What I owned

  • Product planning and requirements verification with the client, then end-to-end implementation
  • Reusable NPM package components (@itc/service-desk) integrated into the company suite, plus a standalone web app separate from the suite
  • Implemented the architect-directed stack: IMAP ingestion, Knockout.js visitor surface, and the multi-channel intake model
  • Laravel BFF (/api/v1/support/*), dual-database persistence (MongoDB tickets + MySQL configuration/RBAC)
  • Vue 3 / Quasar agent workbenches and the shared @itc/service-desk package
  • In-app Vue widget: Sanctum-bound intake, telemetry buffers, screenshot capture and annotation
  • Release V1: dynamic app registry, origin triage, omnichannel continuity

Out of scope

  • High-level architecture direction (technology and channel choices) set by the CEO and company architect
  • Product pricing and commercial strategy (company-owned)
  • Production hosting infrastructure (separate team)

Product context

App Users, Host Developers & Support Agents

One agent inbox for in-app, email and web form support, with diagnostics attached.

Planned, validated and shipped a diagnostics-first, multi-tenant support platform that unifies in-app Vue, IMAP email and public web form intake in one agent inbox, distributed as reusable NPM packages (@itc/service-desk) and a standalone web app.

Core challenge

Evidence: interviews

Portfolio apps reported bugs through scattered emails and hardcoded .env stream mappings, so agents received 'cannot reproduce' tickets with no route, API or error context, and every new app meant a code deploy.

Who hurts:
end-reporterhost-app-developersupport-agent

User benefit matrix

Strategyzer Canvas

The Value Proposition Canvas

Value PropositionService Desk Platform & In-App Vue Widget
Customer SegmentApp Users, Host Devs & Support Agents
Expand value canvas
Products & Services
Gain Creators
Pain Relievers
Gains
Pains
Customer Jobs

Sector

Title

Body

End Reporter (App User / Employee)

Manually describing browser, URL, screen size and steps; separate helpdesk login

designed
Before

Free-text emails with no environment context and a second account to track tickets.

After

One-click filing from inside the app; route, viewport, user agent and annotated screenshot attach automatically via the existing Sanctum session.

Metric: Reporter-typed environment details
Manual description of URL, device and steps→Auto-captured diagnostics pack (schema_version 1)
In-App WidgetScreenshot AnnotatorMy Tickets
personal
Support Agent / Triage Operator

'Cannot reproduce' loops and context switching between app inboxes

designed
Before

Tickets without error messages, API payloads or origin app.

After

Diagnostics tab with the last 20 Axios calls (status, latency, request_id), Vue exceptions, last failed API, plus client_app badges and a multi-select app filter.

Metric: Network context per ticket
None→Last 20 API calls + runtime errors
Diagnostics TabOrigin Triage FilterUnified Inbox
personal
Host App Developer / SaaS Owner

Config sprawl: stream IDs and secrets hardcoded across .env files

designed
Before

SERVICE_DESK_IN_APP_STREAM_IDS redeployed for every new app.

After

support_widget_apps registry and /service-desk/apps admin page map apps to streams with zero code deploys.

Metric: Deploys needed to onboard an app
1 backend deploy per app→0 (self-service registration)
App RegistryAdmin Apps PageOrigin Allow-List
personal

Proof

Impact and product screens

Build

Stack and engineering choices

What I improved

In-app ticket routingdesigned
Before

Stream mapping hardcoded in .env (SERVICE_DESK_IN_APP_STREAM_IDS)

After

Database-backed registry (support_widget_apps) resolving app_id to support_stream_id at request time

How:

New widget middleware on POST /api/v1/support/me/tickets with allowed_origins checks and 400 on unregistered app_id

User benefit:

Developers register new Vue apps in minutes without engineering involvement

Metric: Code deploys per new app
1→0
Agent triage by origindesigned
Before

All app tickets mixed in one undifferentiated feed

After

client_app badge chips on ticket cards and multi-select app filter in ServiceDeskFiltersForm

How:

client_app query parameter appended to GET /tickets

User benefit:

Agents isolate a single product's queue without separate sessions

Metric: Queue isolation per portfolio app
None→Per-app filtered views

Technology

Frontend
  • Vue 3
  • Quasar 2
  • TypeScript
  • Vue Router 4
  • Knockout.js (public form visitor surface)
  • Marker.js (screenshot annotation)
  • Modular Composables
  • @itc/service-desk package
Backend
  • Laravel 11+ (BFF)
  • PHP 8.3
  • Laravel Sanctum
  • Tenant Isolation Middleware
  • Queues & Background Workers
  • IMAP / SMTP Engine
Database
  • MongoDB (ticket documents)
  • MySQL (streams, apps registry, mailboxes, RBAC, forms)
DevOps & Infra
  • Docker Compose
  • Cron Scheduler & Queue Workers
  • Private NPM package publishing
  • Standalone web app deployment

Key decisions

Portfolio apps reported bugs through scattered emails and hardcoded .env stream mappings, so agents received 'cannot reproduce' tickets with no route, API or error context, and every new app meant a code deploy.

Hardcoded .env stream mapping forced a deploy for every new Vue app.

Decision: Moved routing into a support_widget_apps table (app_id, support_stream_id, allowed_origins, is_active) with a widget-only middleware resolving the stream per request.

Outcome: Apps register through /service-desk/apps with zero code deploys; unregistered app_id submissions are rejected with 400.

New widget routing must never break existing email and form intake.

Decision: Scoped the middleware strictly to POST /api/v1/support/me/tickets so SupportEmailIngressService (IMAP) and public form controllers keep native mailbox/form stream mappings.

Outcome: Three channels coexist in one inbox; validated end to end across widget, email and form lifecycles.

'Cannot reproduce' tickets from missing client state.

Decision: Packaged a versioned diagnostics pack (schema_version 1): route metadata, viewport, locale, tenant, a ring-buffer of the last 20 Axios calls with request_id, and trapped window.onerror, unhandled rejections and Vue errors.

Outcome: Every in-app ticket arrives with reproducible context and an isolated last_failed_api.

Diagnostics can leak credentials and PII into tickets.

Decision: Enforced server-side sanitization: drop unmapped keys, cap payload size, redact Authorization, Cookie, Set-Cookie and API keys before persisting as support.context and diagnostics.json.

Outcome: Debug context without storing secrets in the ticket store.

Show 3 more decisions

Support had to work inside the company suite and also as an independent product, without duplicating UI code.

Packaged the agent console and in-app widget as reusable NPM components (@itc/service-desk) consumed by the suite, and built a standalone web app on the same packages.

One shared codebase serves both the integrated suite experience and the standalone product; new host apps embed support by installing the package.

Variable custom ticket fields vs strict relational config.

Dual-database: MongoDB for flexible ticket documents and threads, MySQL for streams, apps, mailboxes, forms and RBAC.

Schema flexibility for custom attributes with relational integrity for administration.

Public replies leaking internal discussion.

Dual-lane threading: public replies trigger sanitized SMTP, private notes stay internal within one chronological thread.

Zero accidental exposure of internal notes to customers.

Trust

Security and roadmap

Security & reliability

  • Sanctum session auth plus tenant-isolation middleware verifying membership and permissions on every API call.
  • Origin allow-list (allowed_origins) and is_active flag per registered app; 400 for unregistered app_id.
  • Server-side payload scrubbing and header redaction for diagnostics packs.
  • Encrypted app secrets and mailbox credentials; never exposed in API responses.
  • Anti-spam on public forms: double opt-in, honeypot, IP rate limits, reCAPTCHA.
  • Isolated queue workers separating IMAP polling from outbound notifications.

Future roadmap

  • MVP 2: SLA sweep engine with breach countdown chips, business-hours routing and round-robin assignment, keyboard triage, real-time WebSocket/SSE feed.
  • MVP 3: screenshot blur/blackout redaction, 'What we will send' opt-out tray, reply email notifications with tokenized reply-by-email, duplicate detection.
  • MVP 4: two-way webhook sync with Jira, Linear and GitHub; FRT and resolution dashboards; CSAT automation; integration failure alerts.
Progressive Disclosure

Architectural Deep-Dive, Schemas & Workflows

Jump:

System Architecture & Domain Scope

Service Desk is a diagnostics-first, multi-tenant support platform I built solo at Proficient Information Systems. The CEO and company architect set the technical direction (IMAP ingestion, Knockout.js for the visitor surface, the channel model, and product expectations); I planned the work, verified requirements with the client, and implemented and shipped the product. It turns the support inbox into a command center for a portfolio of Vue/Quasar apps, with three intake channels feeding one agent workspace.

Distribution Model

  • NPM packages: the agent console, in-app widget and shared composables ship as reusable components (@itc/service-desk), published through the company’s internal NPM system.
  • Suite integration: the packages plug into the company suite, so host apps gain support intake by installing the package and registering an app_id.
  • Standalone web app: a separate deployment of the same packages for use outside the suite.
ChannelIngressTagStream resolution
In-App Vue WidgetPOST /api/v1/support/me/tickets (Sanctum session)in_appapp_id → support_widget_apps → support_stream_id
EmailAsync IMAP polling → SupportEmailIngressServiceemailMailbox configuration
Public Web FormServer-rendered hosted formformForm configuration
flowchart TB
    subgraph Host["Host Vue App (Sanctum)"]
        W["HelpHeaderMount / InAppTicketPanelLayer"]
        T["Telemetry: Axios ring-buffer · router · runtime errors"]
        S["Screenshot capture + annotator"]
    end
    Email["Inbound Email (IMAP)"]
    Form["Public Web Form"]

    subgraph API["Laravel BFF /api/v1/support/*"]
        MW["Widget Middleware: app_id + origin check"]
        REG[("support_widget_apps")]
        SAN["Sanitizer: strip, cap, redact"]
        ING["IMAP Ingress Service"]
        FC["Public Form Controllers"]
    end

    subgraph Data["Persistence"]
        M[("MongoDB: tickets")]
        Q[("MySQL: streams, apps, RBAC")]
    end

    Agent["Agent Console (@itc/service-desk): unified inbox, client_app filter, diagnostics tab"]

    W --> MW
    T --> MW
    S --> MW
    MW --> REG
    MW --> SAN --> M
    Email --> ING --> M
    Form --> FC --> M
    REG --> Q
    M --> Agent

Frontend follows a modular composable architecture (11 authenticated routes under /service-desk/*) with scoped state that resets on tenant switch. The agent console exposes four workbenches: triage inbox, stream queues, form builder and email templates, plus the V1 apps registry.

Persona Value Map

PersonaPains removedShippedRoadmap
End ReporterManual context entry, separate login, no ticket visibilityAuto context capture, screenshot crop and annotation, Sanctum session, My TicketsBlur redaction, data opt-out tray, reply-by-email (MVP 3)
Host DeveloperRebuilding intake per app, .env sprawl, seat costs, PII leaks@itc/service-desk components, app registry, unlimited reporters, server-side redactionNone
Support Agent“Cannot reproduce” loops, multi-app switching, repetitive repliesDiagnostics tab, unified inbox with client_app filter, canned responses and templatesDuplicate detection (MVP 3), Jira/Linear/GitHub sync (MVP 4)

Core Engineering Innovations

1. Database-Driven App Registry (Release V1)

Replaced SERVICE_DESK_IN_APP_STREAM_IDS with a registry table:

support_widget_apps
  id · app_id · support_stream_id (FK → support_streams)
  allowed_origins · is_active · timestamps

Widget middleware on POST /api/v1/support/me/tickets extracts app_id, loads the active record, enforces allowed_origins, and resolves the destination stream. Unregistered or inactive apps return 400 Bad Request. The /service-desk/apps page (SupportWidgetAppsPage, Quasar table plus create/edit modal, validated by FormRequests) lets owners onboard apps with no deploy.

2. Channel Isolation

The middleware applies only to the widget endpoint. IMAP ingestion and public forms keep their native mailbox/form stream mappings, so adding dynamic routing could not regress existing intake.

3. Deterministic Diagnostics Engine (schema_version: 1)

  • Environment auto-capture: route fullPath, meta.title, query params, viewport, user agent, locale, timezone, active tenant.
  • Runtime pack: ring-buffer of the last 20 Axios requests (latency, HTTP status, backend request_id), trapped window.onerror, unhandled promise rejections and Vue exceptions, with last_failed_api isolated.
  • Sanitization pipeline: unmapped keys dropped, size limits enforced, Authorization/Cookie/Set-Cookie/API keys redacted, stored as support.context and a downloadable diagnostics.json.

4. Origin Triage

client_app badge chips on ticket cards and a multi-select filter in ServiceDeskFiltersForm.vue that appends client_app to GET /tickets, isolating one product’s queue inside the shared inbox.

Database Design & Concurrency

  • MongoDB stores ticket documents, custom fields and conversation threads (flexible schema, fast reads).
  • MySQL stores streams, the app registry, mailbox credentials (encrypted), form schemas, templates, RBAC and audit logs.
  • IMAP polling runs on dedicated queue workers with message deduplication and thread matching via In-Reply-To, References and ticket hash, so ingestion never blocks API requests.
  • Tenant isolation middleware verifies membership and permissions on every call; granular gates cover support.view, create, edit, assign, delete and config.

Performance Benchmarks & Reliability

MetricValue
Intake channels unified3
Agent workbenches4 (+ apps registry)
REST endpoints30+
Authenticated routes11
Network calls captured per ticketlast 20
Deploys to onboard a new app0 (was 1)
Release V1 delivery4-day sprint
Delivery surfacesSuite-integrated packages + standalone web app

V1 release validation: end-to-end lifecycle executed across all three channels (widget with screenshot → correct stream; IMAP email → polled into ticket; public form → assigned stream), plus triage validation that email/form tickets display correctly beside widget tickets under the app filter. Feature tests cover /me/* routes and multi-tenant isolation.

Lessons Learned & Architectural Retrospective

  • What worked: scoping middleware to a single endpoint made a routing rewrite low-risk; versioning the diagnostics schema (schema_version) leaves room to evolve the payload.
  • Packaging: building reusable NPM components first meant the suite integration and the standalone app shared one codebase instead of diverging.
  • Trade-off: the dual-database design buys flexibility but adds operational surface (two engines to back up and monitor).
  • Do differently: ship the app registry from day one instead of .env mapping, and add screenshot redaction and the data opt-out tray earlier, since privacy controls matter most for diagnostic capture.
  • Next: SLA sweep engine and automated routing (MVP 2), then privacy controls (MVP 3) and Jira/Linear/GitHub sync (MVP 4).

Case closed

More work.