A complete, headless Service Desk package for Laravel featuring ticket management, SLA tracking, knowledge base, service catalog with approval workflows, and multi-channel email integration.
- Ticket Management - Full lifecycle with status transitions, priorities, assignments, watchers, and auto-generated reference numbers
- SLA Management - Policies with business hours, breach detection, near-breach warnings, pause/resume, and escalation rules
- Knowledge Base - Articles with versioning, publishing workflow, full-text search, feedback collection, and SEO fields
- Service Catalog - Dynamic form builder, multi-step approval workflows, and automatic ticket creation
- Email Integration - Inbound email processing via IMAP, Mailgun, SendGrid, Resend, and Postmark
- Event-Driven - 24 domain events for extensibility
- Notifications - 10 built-in notification classes with queue support
- Translations - English and Brazilian Portuguese included
- Headless - No built-in UI; integrate with Filament, Livewire, Inertia, or any frontend
- PHP 8.2+
- Laravel 11.x or 12.x
Install the package via Composer:
composer require jeffersongoncalves/laravel-service-deskPublish the configuration and migrations:
php artisan vendor:publish --tag="service-desk-config"
php artisan vendor:publish --tag="service-desk-migrations"Run the migrations:
php artisan migrateOptionally publish the translation files:
php artisan vendor:publish --tag="service-desk-translations"The configuration file config/service-desk.php allows you to customize:
'models' => [
'user' => \App\Models\User::class, // Who creates tickets
'operator' => \App\Models\User::class, // Who handles tickets
],'ticket' => [
'reference_prefix' => 'SD', // Ticket references: SD-00001
'default_status' => 'open',
'default_priority' => 'medium',
'allowed_extensions' => ['jpg', 'png', 'pdf', 'doc', 'xlsx', ...],
'max_file_size' => 10240, // KB
'max_attachments_per_comment' => 5,
'attachment_disk' => 'local', // Any Laravel filesystem disk
'auto_close_days' => null, // null = disabled
'allow_reopen' => true,
],'sla' => [
'enabled' => true,
'auto_apply' => true, // Auto-apply policies on ticket creation
'near_breach_minutes' => 30,
'pause_on_statuses' => ['on_hold'],
'default_business_hours_schedule' => null,
],'knowledge_base' => [
'enabled' => true,
'versioning_enabled' => true,
'feedback_enabled' => true,
'track_views' => true,
'search_engine' => 'database', // Or custom implementation
],'service_catalog' => [
'enabled' => true,
'auto_create_ticket' => true, // Auto-create ticket from requests
'approval_enabled' => true,
],'email' => [
'enabled' => true,
'subject_prefix' => '[Service Desk #:reference]',
'threading_enabled' => true,
'inbound' => [
'driver' => env('SERVICE_DESK_INBOUND_DRIVER'), // imap|mailgun|sendgrid|resend|postmark
// Driver-specific settings...
],
],'notifications' => [
'channels' => ['mail'],
'queue' => env('SERVICE_DESK_NOTIFICATION_QUEUE', 'default'),
'notify_on' => [
'ticket_created' => true,
'ticket_assigned' => true,
'ticket_status_changed' => true,
'comment_added' => true,
'sla_breached' => true,
// ...
],
],Add the HasTickets and IsOperator traits to your User model:
use JeffersonGoncalves\ServiceDesk\Concerns\HasTickets;
use JeffersonGoncalves\ServiceDesk\Concerns\IsOperator;
class User extends Authenticatable
{
use HasTickets, IsOperator;
}The ServiceDesk facade provides a clean API for all operations:
use JeffersonGoncalves\ServiceDesk\Facades\ServiceDesk;// Create a ticket
$ticket = ServiceDesk::createTicket([
'title' => 'Cannot login to the system',
'body' => 'I get an error when trying to login...',
'priority' => 'high',
'department_id' => 1,
'category_id' => 2,
], $user);
// Update a ticket
ServiceDesk::updateTicket($ticket, [
'title' => 'Updated title',
'priority' => 'urgent',
], $performer);
// Change status
use JeffersonGoncalves\ServiceDesk\Enums\TicketStatus;
ServiceDesk::changeStatus($ticket, TicketStatus::InProgress, $operator);
// Assign to operator
ServiceDesk::assignTicket($ticket, $operator, $assignedBy);
// Close / Reopen
ServiceDesk::closeTicket($ticket, $performer);
ServiceDesk::reopenTicket($ticket, $performer);
// Find tickets
$ticket = ServiceDesk::findTicketByUuid('550e8400-e29b-41d4-a716-446655440000');
$ticket = ServiceDesk::findTicketByReference('SD-00001');
// Watchers
ServiceDesk::addWatcher($ticket, $user);
ServiceDesk::removeWatcher($ticket, $user);// Add a public reply
ServiceDesk::addComment($ticket, $author, 'Thanks for reporting this!', [
'attachments' => $uploadedFiles, // Optional
]);
// Add an internal note (not visible to the user)
ServiceDesk::addNote($ticket, $operator, 'Escalating to tier 2 support.');// Create a department
$department = ServiceDesk::createDepartment([
'name' => 'IT Support',
'description' => 'Handles IT-related tickets',
'is_active' => true,
]);
// Manage operators
ServiceDesk::addOperator($department, $operator, 'manager');
ServiceDesk::removeOperator($department, $operator);For advanced operations, access the underlying services:
$ticketService = ServiceDesk::tickets();
$commentService = ServiceDesk::comments();
$departmentService = ServiceDesk::departments();
$attachmentService = ServiceDesk::attachments();All enums support translated labels via the label() method:
use JeffersonGoncalves\ServiceDesk\Enums\TicketStatus;
use JeffersonGoncalves\ServiceDesk\Enums\TicketPriority;
use JeffersonGoncalves\ServiceDesk\Enums\CommentType;
TicketStatus::Open->label(); // "Open" (en) / "Aberto" (pt_BR)
TicketPriority::High->label(); // "High" (en) / "Alta" (pt_BR)
CommentType::Reply->label(); // "Reply" (en) / "Resposta" (pt_BR)
// Status transitions
TicketStatus::Open->allowedTransitions(); // [Pending, InProgress, OnHold, ...]
TicketStatus::Open->canTransitionTo(TicketStatus::InProgress); // true
TicketStatus::Closed->canTransitionTo(TicketStatus::InProgress); // false
// Priority numeric value (useful for sorting)
TicketPriority::Low->numericValue(); // 1
TicketPriority::Urgent->numericValue(); // 4| Enum | Cases |
|---|---|
TicketStatus |
Open, Pending, InProgress, OnHold, Resolved, Closed |
TicketPriority |
Low, Medium, High, Urgent |
TicketSource |
Web, Email, Api, ServiceRequest, Phone, Chat |
CommentType |
Reply, Note, System |
HistoryAction |
Created, StatusChanged, PriorityChanged, Assigned, ... |
ArticleStatus |
Draft, Published, Archived |
ArticleVisibility |
Public, Internal |
ServiceCategoryVisibility |
Public, Internal, Draft |
ServiceRequestStatus |
Pending, Approved, Rejected, InProgress, Fulfilled, Cancelled |
ApprovalStatus |
Pending, Approved, Rejected |
FormFieldType |
Text, Textarea, Select, Checkbox, Radio, Date, ... |
SlaBreachType |
FirstResponse, NextResponse, Resolution |
EscalationAction |
Notify, Reassign, ChangePriority, Custom |
DayOfWeek |
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday |
The package dispatches domain events that you can listen to:
| Event | Dispatched When |
|---|---|
TicketCreated |
A new ticket is created |
TicketUpdated |
A ticket is updated |
TicketStatusChanged |
Ticket status changes |
TicketAssigned |
Ticket is assigned to an operator |
TicketPriorityChanged |
Ticket priority changes |
TicketClosed |
A ticket is closed |
TicketReopened |
A closed ticket is reopened |
TicketDeleted |
A ticket is deleted |
CommentAdded |
A comment is added to a ticket |
AttachmentAdded |
An attachment is uploaded |
AttachmentRemoved |
An attachment is removed |
SlaApplied |
An SLA policy is applied to a ticket |
SlaBreached |
An SLA target is breached |
SlaNearBreach |
An SLA target is near breach |
SlaMetricMet |
An SLA target is met |
EscalationTriggered |
An escalation rule fires |
ArticleCreated |
A knowledge base article is created |
ArticlePublished |
An article is published |
ArticleFeedbackReceived |
Article feedback is submitted |
ServiceRequestCreated |
A service request is submitted |
ServiceRequestStatusChanged |
Service request status changes |
ApprovalRequested |
Approval is requested for a service request |
ApprovalDecisionMade |
An approval decision is made |
InboundEmailReceived |
An inbound email is received |
InboundEmailProcessed |
An inbound email is processed |
// In your EventServiceProvider or listener
use JeffersonGoncalves\ServiceDesk\Events\TicketCreated;
class NotifySlackOnTicketCreation
{
public function handle(TicketCreated $event): void
{
$ticket = $event->ticket;
// Send Slack notification...
}
}| Command | Description |
|---|---|
service-desk:poll-imap-mailbox |
Poll IMAP mailbox for new emails |
service-desk:clean-inbound-emails |
Delete old inbound emails past retention |
service-desk:close-stale-tickets |
Auto-close tickets without recent activity |
service-desk:check-sla-breaches |
Detect and mark SLA breaches |
service-desk:process-escalations |
Execute escalation rules for breached tickets |
service-desk:recalculate-sla |
Recalculate SLA due dates |
Add these to your routes/console.php or scheduler:
use Illuminate\Support\Facades\Schedule;
Schedule::command('service-desk:check-sla-breaches')->everyFiveMinutes();
Schedule::command('service-desk:process-escalations')->everyFiveMinutes();
Schedule::command('service-desk:poll-imap-mailbox')->everyFiveMinutes();
Schedule::command('service-desk:close-stale-tickets')->daily();
Schedule::command('service-desk:clean-inbound-emails')->daily();Requires the optional webklex/php-imap package:
composer require webklex/php-imapConfigure in .env:
SERVICE_DESK_INBOUND_DRIVER=imap
SERVICE_DESK_IMAP_HOST=imap.example.com
SERVICE_DESK_IMAP_PORT=993
SERVICE_DESK_IMAP_ENCRYPTION=ssl
SERVICE_DESK_IMAP_USERNAME=support@example.com
SERVICE_DESK_IMAP_PASSWORD=your-password
SERVICE_DESK_IMAP_FOLDER=INBOXConfigure the webhook driver and point your email provider's webhook URL to:
https://your-app.com/service-desk/webhooks/{driver}
Where {driver} is mailgun, sendgrid, resend, or postmark.
Each driver includes signature verification middleware for security.
The package ships with English and Brazilian Portuguese translations. To customize or add new languages, publish the translation files:
php artisan vendor:publish --tag="service-desk-translations"Translation files will be published to lang/vendor/service-desk/.
composer testcomposer analysecomposer formatPlease see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.
