Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Help Desk Laravel Package

jeffersongoncalves/laravel-help-desk

Laravel Help Desk is a complete ticket management system for Laravel 11–13 with email integration. Create departments, assign operators, and manage ticket statuses, priorities, attachments, and notifications through a simple, configurable package.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. Unchanged in shape, and v1.13.0 strengthens the most important part of it. The package is a domain-in-a-box for Laravel: five or six Eloquent models (tickets, comments, attachments, departments, watchers, history) behind a single HelpDesk facade. That facade remains the stable seam — if we later replace the package with a commercial help desk or an in-house build, call sites (HelpDesk::createTicket, addComment, changeStatus) survive a rewrite and only the facade implementation changes. That is still the single biggest argument in favour of piloting it.

The second architectural fact is the two-driver design, and this release does not change it. driver: database gives full capability but requires the satellite application to hold credentials for the support database. driver: api gives a signed-HMAC HTTP boundary where the satellite holds only a shared secret, but supports the end-user side only — operator actions (closeTicket, assignTicket, addNote) throw before making a request, Eloquent scopes don't exist because there are no local tables, and attachment reads throw because there is no disk to serve from. The release note "Reads remain scoped to the requester" is an explicit confirmation that the capability boundary is intact: a satellite can now file a ticket with a company and read back its company_name, and still cannot run an agent console. That is not a minor caveat; it is the decision that shapes the whole deployment topology.

The third strength — no foreign key to users, identity snapshotted into metadata at write time — is now the design pattern the package is deliberately extending. company_name lands in metadata.company.name alongside the requester's name, $ticket->company_name reads it with a fallback to company_id, and there is still no companies table. This is unusually mature for a package this size: the support database can remain a standalone service that knows nothing about any application's user or company tables, tickets from several products and several customers can coexist, and history does not silently rewrite itself when a user is renamed, a company is rebranded, or a tenant is decommissioned. Two consequences a TPM should internalise now:

  • company_id is a label, not a relationship. There is no referential integrity, no company model, no cascade, and — critically — no company-level visibility enforcement. "Which tickets can an agent see?" is not answered by this package. If we need tenant segmentation in the agent console, that is our build, not theirs.
  • The snapshot is write-time only. Tickets created before v1.13.0 show the id. There is no backfill migration (No migration needed is stated explicitly), so any historical display cleanup is a data edit we own.

Integration feasibility. The release is additive and non-breaking: one new optional createTicket() argument, one new field on the API POST and on TicketResource, docs, and CI. No migration, no config change required, no deprecation. Upgrade effort from v1.12.x is a composer update plus a smoke test. Semver has been respected, which is itself a useful signal about upgrade hygiene.

The install path remains conventional (composer require, publish config, publish migrations, migrate, add traits to User), with the same invasive step: HasTickets on requester models and IsOperator on agent models means editing the application's User class — a shared file in any monolith and a coordination point across teams. The new ability to map a differently named column (e.g. empresa_id) via an accessor is the same pattern as the existing toHelpDeskSnapshot() override, and it means a non-standard schema is not a blocker — but it is code we write and own.

The genuinely sharp edges are unchanged, and one is now slightly wider:

  • Morph relations are fatal, not null. $ticket->user, $comment->author, $attachment->uploadedBy, $entry->performer, $row->watcher all raise Class "..." not found if the stored morph type names a class the current app doesn't have. Every read of a person must go through requester_name, author_name, performer_name, resolvedAuthor() and friends. This is a coding standard, not a one-off fix — it needs to be in the PR checklist or it will be violated by the first developer who joins after the pilot. Note the company snapshot has no equivalent trap because it stores scalars, not morphs; that is a point in its favour.
  • Morph aliases must be enforced. Any multi-app deployment without Relation::enforceMorphMap(['app-a-user' => ...]) will collide user #5 across applications. One-line AppServiceProvider change, very high blast radius if forgotten.
  • Multi-company collision is now a live concern too. company_id is a string with no uniqueness guarantee across applications. Two products both using company_id = "42" will produce tickets that look like one customer. The new README section documents setting and querying company_id and flags a null company caveat, but it does not mandate a namespacing convention. That decision is ours.
  • Migrations must run in exactly one place. The central application owns the schema; satellites install the package but must not publish or run migrations, because Laravel tracks applied migrations per-connection and satellites would attempt to recreate existing tables.
  • Status transitions are validated and throw. InvalidStatusTransitionException on illegal transitions; closed tickets reopen only to Open. Good domain modelling, but any UI or import script assuming free-form status setting fails at runtime, not at design time.

Technical risk. The dominant risk was, and remains, bus factor and ecosystem signal — one maintainer, no vendor to call, no peer community of any size. But v1.13.0 is a genuine de-risking datapoint on three axes: the project is shipping (issue numbers #80–#82 and a full compare link imply real commit and review history, so the earlier "verify the release date is real" concern is largely closed), the changes are semver-clean and migration-free, and the new standardized tests workflow plus Dependabot config materially lowers the cost of the fork contingency — a fork we inherit with CI and a test suite is far cheaper to carry than a fork with neither. Tempering that: github-actions auto-merge on a single-maintainer repo is a supply-chain consideration worth noting, and auto-merged dependency bumps are not the same as reviewed domain changes. The star count and dependent count should still be re-verified against the repository, because this changelog tells us nothing about adoption.

Risk Severity Mitigation
Single maintainer, abandonment High (was High; slightly reduced by active releases + CI) Facade seam + fork rights under MIT; CI/test workflow now inherited with any fork; ~5 days/quarter contingency
Laravel/PHP version support unverified High Pin major version; verify support matrix against our LTS target before committing
Cross-company data conflation (company_id unnamespaced) Medium-High (new) Namespacing convention in the ADR (e.g. app-key:company-id), enforced by app-side validation
Agent visibility not scoped by company Medium-High (new) Build the scoping ourselves in the agent console; do not assume the package provides tenancy
Morph-relation fatal reads in app code Medium Lint rule / code review checklist; accessor-only convention documented
Two-driver capability asymmetry misunderstood Medium Architecture decision record; explicit statement of what each satellite can and cannot do
Email inbound integration depth unproven Medium Spike before committing to "email integration" as a delivered feature
Reference number generation under concurrency Medium Load test ticket creation; confirm DB-level uniqueness on reference_number
Attachment disk defaults to local Medium Configure shared/S3 disk for any multi-node or multi-app deployment
Legacy tickets display company id, not name Low Accept, or script a one-off metadata.company.name backfill; decide at cutover

Key questions. Revised where v1.13.0 has moved the answer.

  1. What is the supported Laravel and PHP range, and does it cover our LTS target? (The "is it maintained" question is now largely answered: it shipped a clean minor release with CI.)
  2. Who owns the package if the maintainer stops? With CI and tests now in the repo, does that change our appetite to fork and carry a private patch set downward?
  3. Is the support database shared across products, or one per product? This decides driver and scope_to_app for every application.
  4. Which applications need operator capability versus end-user capability? Every api-driver satellite is permanently read-and-create only — unchanged by this release.
  5. New: What is our company_id namespacing convention, and who validates it at the facade boundary? Without one, cross-product tenant collision is a data-integrity incident waiting to happen.
  6. New: Does the business need company-level visibility in the agent console, or is company purely an attribute for reporting? The package gives us the latter only; the former is our build and should be estimated as such.
  7. What is the real depth of the email integration — inbound parsing, threading/reply matching, bounce handling?
  8. Does our existing ticket lifecycle map cleanly onto the enforced state machine, or will we be fighting transition validation?
  9. Are we prepared to operate attachments on S3 from day one, or are we accepting single-node local storage?
  10. Is a help desk a product we're buying or an internal tool we're building? The answer changes the staffing model, not just the code.

Integration Approach

Stack fit. A natural fit for a Laravel-first estate. If the applications in scope are Laravel on the same major version, integration is idiomatic: no new runtime, no new language, no new deployment unit, no separate service to operate. If the estate is polyglot, the signed API driver is the escape hatch — any non-PHP satellite can speak HMAC-signed HTTP to a Laravel central support app. v1.13.0 improves that path slightly: POST tickets accepts company_id and company_name, and integer company_id is cast to string, which removes a class of type-mismatch friction for a typed client. Worth flagging unchanged: the API driver's inbound usefulness is limited to the end-user side, so a non-PHP satellite can file tickets but cannot run an agent console.

Migration path. Three lanes, in ascending cost, with one new sub-task:

  • Greenfield internal tool. No migration. Install, add traits, seed departments, ship a thin Blade/Livewire UI over the facade. Lowest risk, fastest to value, recommended pilot.
  • Consolidation. Multiple products each have a spreadsheet, shared inbox, or bespoke ticket table. Central support app on database driver owning the schema; satellites on api driver. One-way ETL per product: map old tickets into createTicket with preserved created_at, accept that history before cutover is a backfill with synthetic system comments. Added: decide per source system whether legacy tickets get a company_name snapshot or are left displaying the id — and if backfilled, whether the backfill is worth the write against an audit table. Budget one iteration per source system; legacy status vocabularies rarely map one-to-one onto Open / InProgress / Resolved / Closed, and transition validation will reject the unmappable ones.
  • Replace a commercial help desk. Highest cost, and a package at this adoption level is unlikely to win that evaluation. If the driver is cost reduction rather than capability, scope it to a single team or product first.

Critical sequencing rule for any lane touching more than one app: the schema owner goes first and alone. Central application publishes config and migrations, runs them, and proves the schema is stable before any satellite is configured. Satellites then install with HELPDESK_DB_CONNECTION (or HELPDESK_DRIVER=api) and explicitly do not run migrations. v1.13.0 makes this easier — it needs no migration at all — but it does not relax the rule, and the version skew it implies (some satellites on 1.12, some on 1.13) is harmless precisely because the change is additive.

Compatibility. Three axes now, two of them unchanged. First, Laravel and PHP version support — still unverified, still the most likely cause of an early, cheap failure. Second, our own authentication and user model: the trait-based integration assumes a conventional App\Models\User; if permissions live in a custom guard, a separate Agent model, or an SSO-only identity with no local record, IsOperator and the snapshot mechanism need adaptation, including a toHelpDeskSnapshot() override. Third, new: our company/tenant schema. The README explicitly supports mapping a differently named column (empresa_id) via an accessor, so a non-standard schema is not a blocker — but it is integration code with a test obligation, and it must also cover the case where a ticket has no company at all (the documented null caveat). Confirm what the agent console should display for null before UAT, not during it.

Notification delivery remains the forgotten compatibility point. notifyRequester() resolves through the model when possible and falls back to an on-demand mail to the snapshot address, so outbound email configuration, from-address reputation, and queue workers are all in scope. If mail is synchronous today, ticket creation will feel slow and needs a queue.

Sequencing. Recommended order, with the release folded in:

  1. Spike, two weeks. Stand up a throwaway Laravel app on v1.13.0, install, run migrations, exercise create → assign → comment → attach → close → reopen, create one ticket with company_id/company_name and one without, and specifically test the email inbound path. Confirm the version matrix. Output: go/no-go with a real effort estimate rather than an estimated effort.
  2. Architecture decision record. Choose driver topology, schema owner, morph alias strategy, attachment disk, scope_to_app policy — and now company_id namespacing, the null company display rule, and whether company-level visibility is in scope or explicitly deferred.
  3. Central application, vertical slice. Schema, departments, operator roles, one end-to-end ticket flow, history logging verified (confirm the default LogTicketHistory listener is registered — history silently absent is a nasty late discovery), attachment storage on S3.
  4. Conventions hardening. Publish the accessor-only rule for person reads, wire Relation::enforceMorphMap into AppServiceProvider::boot(), add the review checklist item. Do this before the second developer touches the code.
  5. First satellite, api driver. Deliberately pick the least complex product. Goal: prove the signed-API path, secret configuration, company_id/company_name round-trip, and the capability boundary in the smallest possible blast radius.
  6. Remaining satellites, one per iteration. Each with its own secret pair (current + previous, so rotation needs no flag day) and, for database-driver satellites, a documented "no migrations" rule.
  7. UI and workflow polish. Agent console, saved views on the model scopes (open(), overdue(), unassigned(), byPriority()), SLA surfaces from due_at, and — if in scope — company-scoped filtering built by us.
  8. Legacy backfill, last, once the new flow is stable — never before, or you'll migrate data into a schema you're still changing.

Operational Impact

Maintenance. Low steady-state code maintenance; non-trivial upgrade maintenance, though v1.13.0 is the good case — additive, no migration, no config change, semver-clean. The package surface we touch stays narrow (facade calls, two traits, one config file), MIT licensing preserves the fork option, and the standardized tests workflow plus Dependabot config reduce the cost of exercising it. Budget realistically: a dependency review each Laravel upgrade cycle, plus a named owner. The maintenance cost that catches teams out is not the package — it's the surrounding operations: attachment storage growth and lifecycle policy, outbound mail deliverability, queue monitoring for notifications, and the schema. Someone has to own "the tickets table grew 40% this quarter," and someone has to own the company_id convention as new products onboard.

Support. Two distinct support populations needing different answers. End users need to know how to file and track a ticket — v1.13.0 changes nothing here. Agents need to understand the state machine, because the package enforces it and will reject workarounds; the failure mode to pre-empt is an agent trying to move a Closed ticket to InProgress and being refused — that's a policy the package has already made for us and needs to be communicated as a deliberate rule, not a bug. Add a third, smaller population: whoever maintains the company mapping. A mis-set empresa_id accessor silently produces blank or wrong company names on every new ticket, and no one notices until a report is wrong. Internal first-line support also needs a documented escalation path to the fork-contingency owner, because there is no vendor to call.

Scaling. The read path scales like any Laravel app — Eloquent over a tickets table. The concerns are specific and predictable:

  • Ticket table growth. Ticket + comments + history + attachments rows per conversation. History is written on every status change, assignment, comment and attachment, so history is often the largest table. Index for status, assigned_to, due_at, and app_key before volume arrives, not after. company_id will become a common filter — evaluate an index once reporting demand appears, but don't add it speculatively; it lives in a JSON column and the query shape matters.
  • Attachment storage dominates cost and surprises finance. S3 lifecycle rules and a retention policy tied to the ticket lifecycle (what happens to attachments on a soft-deleted ticket?) should be decided at design time.
  • The central app is a single point of failure for every satellite in the api topology. If it's down, satellite users cannot file tickets. Usually acceptable for an internal help desk — but an accepted risk, not an unexamined one.
  • HMAC verification is cheap; the rate-limit story is unverified and should be a spike question.
  • Reference number generation under concurrent creation needs a confirmed database-level uniqueness constraint. If HD-00001 is computed by counting rows, that's a real collision risk at volume.
  • Multi-company volume is a reporting problem before it is a storage problem. If the agent console must slice by company, and the package gives no index or scope for it, that's our query to optimise.

Failure modes. Ranked by likelihood × impact, with the two new entries marked.

Failure Symptom Detection Response
Morph relation read directly Class not found fatal on a ticket page Code review + static analysis Fix at call site; accessor-only rule
Cross-app morph collision Wrong person's name on a ticket Manual spot-check Retroactive morph map — data repair required, so prevent it
Cross-company company_id collision (new) Two different customers appear as one in reports Reporting discrepancy, found late Namespacing convention at the facade boundary; retroactive repair is a data edit
Company visibility assumed but absent (new) Agent sees another tenant's tickets Security review / UAT Build scoping explicitly; treat as an accepted gap until built
Satellite ran migrations Table-already-exists errors, partial schema Deploy-time failure "No migrations" per satellite; deploy checklist
History silently empty No audit trail, discovered at audit time Verify at slice 3, not at launch Confirm LogTicketHistory registration
Attachment URL throws Images broken on satellite UAT Shared disk / S3; getTemporaryUrl requires S3, not local
Illegal status transition Agent blocked mid-workflow Agent complaint Communicate the state machine as policy; provide reopen path
Mail not delivered Requester never notified Bounce monitoring Queue workers, from-address reputation, SPF/DKIM
Version skew across satellites Inconsistent company_name presence in responses Integration test Additive release, so tolerate skew; pin per app and roll forward
Upstream abandonment Security patch unavailable Dependency review Fork under MIT; CI now inherited; ~5 days/quarter contingent
Secret leak on api driver Attacker impersonates any user of one app Secret rotation Two-secret rotation reduces flag day, not exposure window; HTTPS mandatory since HMAC gives authenticity, not confidentiality

Ramp-up. Roughly one sprint for a Laravel-fluent developer to be productive, and v1.13.0 slightly reduces the reading burden by documenting the multi-company path in one place. Cost is concentrated in two places: teaching the accessor-only rule for person reads (a 30-minute briefing that prevents a class of production fatals), and teaching the multi-app topology — who owns the schema, why satellites don't migrate, why morph aliases exist, and now what company_id means and does not mean. That second one is institutional knowledge that evaporates when the person who read the docs leaves. Write it down.

For operators and support staff, ramp-up is a half-day on the ticket lifecycle, the department/enquiry model, and — new — how to read a company field that may be blank, an id, or a name depending on ticket age. That inconsistency is small but it generates support questions, so it belongs in the agent one-pager.

For the TPM, the ramp-up cost is the evaluation itself. This package is cheap to trial and cheap to abandon, but only if we hold the facade seam and resist reaching past it into the models. v1.13.0 improves the picture modestly and in the right direction — active shipping, CI, snapshot-consistent design, no breaking change — while leaving the two questions that actually decide the programme untouched: the Laravel version matrix and the depth of email integration. It also surfaces a question the earlier assessment didn't have: whether company is a label or a tenancy boundary. The package gives us the label for free. If the business thinks it's buying the boundary, we will build the boundary ourselves, and that should be sized before any steering-group commitment — which is why the recommendation stands: a bounded two-week spike on v1.13.0 with a pre-agreed go/no-go, gated on the version matrix, email depth, and a decision on what company scope has to mean.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums