jeffersongoncalves / laravel-help-desk
A comprehensive help desk and ticket management system for Laravel applications with email integration.
Installs: 1
Dependents: 1
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
pkg:composer/jeffersongoncalves/laravel-help-desk
Requires
- php: ^8.1
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/events: ^10.0|^11.0|^12.0
- illuminate/notifications: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.0
- nunomaduro/larastan: ^2.0
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
- webklex/php-imap: Required for IMAP inbound email polling (^5.0)
This package is auto-updated.
Last update: 2026-02-04 03:00:50 UTC
README
A comprehensive help desk and ticket management system for Laravel applications with email integration.
Requirements
- PHP 8.1+
- Laravel 10, 11, or 12
Installation
composer require jeffersongoncalves/laravel-help-desk
The package uses Laravel's auto-discovery, so the service provider and facade are registered automatically.
Publish Configuration
php artisan vendor:publish --tag=help-desk-config
Publish Migrations
php artisan vendor:publish --tag=help-desk-migrations
Run Migrations
php artisan migrate
Publish Translations (optional)
php artisan vendor:publish --tag=help-desk-translations
Configuration
The configuration file is located at config/help-desk.php. Key options:
return [ // Models used by the help desk 'models' => [ 'user' => \App\Models\User::class, // Model that creates tickets 'operator' => \App\Models\User::class, // Model that manages tickets ], // Ticket settings 'ticket' => [ 'reference_prefix' => 'HD', // Ticket reference format: HD-00001 'default_status' => 'open', 'default_priority' => 'medium', 'attachment_disk' => 'local', // Storage disk for attachments 'auto_close_days' => null, // Auto-close resolved tickets (null = disabled) 'allow_reopen' => true, ], // Email integration 'email' => [ 'enabled' => true, 'inbound' => [ 'driver' => null, // 'imap', 'mailgun', 'sendgrid', or 'resend' ], ], // Notification settings 'notifications' => [ 'channels' => ['mail'], 'queue' => 'default', ], ];
Setup
1. Add Traits to Your User Model
For regular users (ticket creators):
use JeffersonGoncalves\HelpDesk\Concerns\HasTickets; class User extends Authenticatable { use HasTickets; }
For operators/agents (ticket managers):
use JeffersonGoncalves\HelpDesk\Concerns\IsOperator; class User extends Authenticatable { use IsOperator; // Includes HasTickets }
2. Create Departments
use JeffersonGoncalves\HelpDesk\Facades\HelpDesk; $department = HelpDesk::createDepartment([ 'name' => 'Technical Support', 'slug' => 'technical-support', 'email' => 'support@example.com', 'is_active' => true, ]);
3. Assign Operators to Departments
HelpDesk::addOperator($department, $user, 'operator'); // 'operator', 'manager', or 'admin'
Usage
Creating Tickets
use JeffersonGoncalves\HelpDesk\Facades\HelpDesk; $ticket = HelpDesk::createTicket([ 'title' => 'Cannot access my account', 'description' => 'I get an error when trying to log in...', 'department_id' => $department->id, 'priority' => 'high', ], $user); // $ticket->reference_number => "HD-00001" // $ticket->uuid => "550e8400-e29b-41d4-a716-446655440000"
Managing Tickets
// Find tickets $ticket = HelpDesk::findTicketByReference('HD-00001'); $ticket = HelpDesk::findTicketByUuid('550e8400-...'); // Assign to operator HelpDesk::assignTicket($ticket, $operator); HelpDesk::unassignTicket($ticket); // Change status use JeffersonGoncalves\HelpDesk\Enums\TicketStatus; HelpDesk::changeStatus($ticket, TicketStatus::InProgress); HelpDesk::closeTicket($ticket); HelpDesk::reopenTicket($ticket); // Update ticket HelpDesk::updateTicket($ticket, [ 'priority' => 'urgent', 'category_id' => $category->id, ]); // Delete ticket (soft delete) HelpDesk::deleteTicket($ticket);
Comments
// Add a public reply $comment = HelpDesk::addComment($ticket, $user, 'Thank you for contacting us.'); // Add an internal note (not visible to end user) $note = HelpDesk::addNote($ticket, $operator, 'Escalating to senior engineer.'); // Add comment with attachments $comment = HelpDesk::addComment($ticket, $user, 'See attached screenshot.', [ 'attachments' => [$uploadedFile], ]);
Watchers
HelpDesk::addWatcher($ticket, $anotherUser); HelpDesk::removeWatcher($ticket, $anotherUser);
Querying Tickets
use JeffersonGoncalves\HelpDesk\Models\Ticket; use JeffersonGoncalves\HelpDesk\Enums\TicketStatus; use JeffersonGoncalves\HelpDesk\Enums\TicketPriority; // Open tickets $open = Ticket::open()->get(); // Closed tickets $closed = Ticket::closed()->get(); // By status $inProgress = Ticket::byStatus(TicketStatus::InProgress)->get(); // By priority $urgent = Ticket::byPriority(TicketPriority::Urgent)->get(); // Overdue tickets $overdue = Ticket::overdue()->get(); // Unassigned tickets $unassigned = Ticket::unassigned()->get(); // User's tickets (via trait) $user->helpDeskTickets; // Operator's assigned tickets (via trait) $operator->helpDeskAssignedTickets;
Canned Responses
use JeffersonGoncalves\HelpDesk\Models\CannedResponse; CannedResponse::create([ 'title' => 'Greeting', 'body' => 'Thank you for contacting our support team...', 'department_id' => $department->id, 'is_active' => true, ]); // Get canned responses for a department $responses = CannedResponse::active() ->forDepartment($department->id) ->ordered() ->get();
Categories
use JeffersonGoncalves\HelpDesk\Models\Category; $category = Category::create([ 'department_id' => $department->id, 'name' => 'Billing', 'slug' => 'billing', 'is_active' => true, ]); // Subcategories $sub = Category::create([ 'department_id' => $department->id, 'parent_id' => $category->id, 'name' => 'Refunds', 'slug' => 'refunds', ]);
Ticket Statuses
| Status | Description |
|---|---|
open |
New ticket, awaiting response |
pending |
Awaiting user response |
in_progress |
Being worked on by an operator |
on_hold |
Temporarily on hold |
resolved |
Issue has been resolved |
closed |
Ticket is closed |
Status transitions are validated automatically. For example, a closed ticket can only transition to open (reopen).
Ticket Priorities
| Priority | Numeric Value |
|---|---|
low |
1 |
medium |
2 |
high |
3 |
urgent |
4 |
Events
The package dispatches events that you can listen to in your application:
| Event | Description |
|---|---|
TicketCreated |
A new ticket was created |
TicketUpdated |
A ticket was updated |
TicketStatusChanged |
Ticket status changed |
TicketPriorityChanged |
Ticket priority changed |
TicketAssigned |
Ticket was assigned to an operator |
TicketClosed |
Ticket was closed |
TicketReopened |
Ticket was reopened |
TicketDeleted |
Ticket was deleted |
CommentAdded |
A comment was added to a ticket |
AttachmentAdded |
An attachment was added |
AttachmentRemoved |
An attachment was removed |
InboundEmailReceived |
An inbound email was received |
InboundEmailProcessed |
An inbound email was processed |
Disabling Default Listeners
If you want to handle events yourself:
// config/help-desk.php 'register_default_listeners' => false,
Email Integration
Outbound Notifications
Notifications are sent automatically when events occur (configurable via notifications.notify_on). Email threading is supported via Message-ID, In-Reply-To, and References headers.
Inbound Email
The package supports receiving emails via 5 drivers:
IMAP
HELPDESK_INBOUND_DRIVER=imap HELPDESK_IMAP_HOST=imap.example.com HELPDESK_IMAP_PORT=993 HELPDESK_IMAP_ENCRYPTION=ssl HELPDESK_IMAP_USERNAME=support@example.com HELPDESK_IMAP_PASSWORD=your-password HELPDESK_IMAP_FOLDER=INBOX
Requires the webklex/php-imap package:
composer require webklex/php-imap
Schedule the polling command in your app/Console/Kernel.php or routes/console.php:
$schedule->command('help-desk:poll-imap')->everyFiveMinutes();
Mailgun
HELPDESK_INBOUND_DRIVER=mailgun HELPDESK_MAILGUN_SIGNING_KEY=your-signing-key
Configure your Mailgun route to forward to:
POST https://your-app.com/help-desk/webhooks/mailgun
SendGrid
HELPDESK_INBOUND_DRIVER=sendgrid HELPDESK_SENDGRID_WEBHOOK_USERNAME=your-username HELPDESK_SENDGRID_WEBHOOK_PASSWORD=your-password
Configure your SendGrid Inbound Parse to forward to:
POST https://your-app.com/help-desk/webhooks/sendgrid
Resend
HELPDESK_INBOUND_DRIVER=resend HELPDESK_RESEND_API_KEY=re_your-api-key HELPDESK_RESEND_WEBHOOK_SECRET=whsec_your-webhook-secret
Configure your Resend receiving domain webhook to forward to:
POST https://your-app.com/help-desk/webhooks/resend
Select the email.received event type in your Resend webhook configuration.
Postmark
HELPDESK_INBOUND_DRIVER=postmark HELPDESK_POSTMARK_WEBHOOK_USERNAME=your-username HELPDESK_POSTMARK_WEBHOOK_PASSWORD=your-password
In your Postmark server, go to the Inbound Message Stream settings and set the webhook URL to:
POST https://your-username:your-password@your-app.com/help-desk/webhooks/postmark
Postmark sends the full email content (body, headers, attachments) directly in the webhook payload. The package also uses Postmark's StrippedTextReply field for cleaner reply parsing.
Email Channels
You can configure multiple email channels, each mapped to a department:
use JeffersonGoncalves\HelpDesk\Models\EmailChannel; EmailChannel::create([ 'department_id' => $department->id, 'name' => 'Support Inbox', 'driver' => 'mailgun', 'email_address' => 'support@example.com', 'settings' => [], // Driver-specific settings (encrypted) 'is_active' => true, ]);
Email Threading
When an inbound email is received, the package resolves it to an existing ticket using:
In-Reply-ToheaderReferencesheader- Subject line reference number (e.g.,
HD-00001)
If no match is found, a new ticket is created.
Artisan Commands
# Poll IMAP mailboxes for new emails php artisan help-desk:poll-imap # Clean old processed inbound emails php artisan help-desk:clean-emails --days=30 # Auto-close stale tickets php artisan help-desk:close-stale --days=14 --status=resolved # Dry run (see what would be closed) php artisan help-desk:close-stale --days=14 --dry-run
Using the Services Directly
For more control, you can inject the service classes directly:
use JeffersonGoncalves\HelpDesk\Services\TicketService; use JeffersonGoncalves\HelpDesk\Services\CommentService; use JeffersonGoncalves\HelpDesk\Services\DepartmentService; use JeffersonGoncalves\HelpDesk\Services\AttachmentService; class MyController { public function __construct( private TicketService $tickets, private CommentService $comments, ) {} public function store(Request $request) { $ticket = $this->tickets->create([ 'title' => $request->title, 'description' => $request->description, 'department_id' => $request->department_id, ], $request->user()); return $ticket; } }
Translation
The package ships with English and Brazilian Portuguese translations. To customize:
php artisan vendor:publish --tag=help-desk-translations
This publishes translation files to lang/vendor/help-desk/. You can modify them or add new locales.
// Using translations in your code __('help-desk::tickets.messages.created') // "Ticket created successfully." __('help-desk::statuses.open') // "Open" __('help-desk::priorities.urgent') // "Urgent"
Testing
composer test
Static Analysis
composer analyse
Code Formatting
composer format
License
The MIT License (MIT). Please see License File for more information.