Default driver
Sends ticket details to the support team mailbox and a confirmation to the requester.
- Generates
SUP-{id}style references - Queued via Laravel’s mail system
- Also serves as the automatic fallback
The starter includes a user-facing contact support form that submits tickets to a configurable backend. It ships with two gateway drivers, Email and TeamDynamix, and an automatic email fallback that ensures user requests are never lost.
The feature is disabled by default and can be enabled with a single environment variable.
The system follows a layered architecture:
TicketSystemGateway interface decouples orchestration from gateway implementationsTicketSystem::gatewayClass() maps each driver to its concrete classCreationResult carries the outcome of each gateway callCreateSupportTicket orchestrates the workflow in one placeSUPPORT_ENABLED=trueSUPPORT_DRIVER=mailSUPPORT_MAIL_TO=your-team@northwestern.eduWhen enabled, the contact form routes are registered and the submission log appears in the Filament admin panel. When disabled, no routes are registered and the feature is completely inert.
User visits the contact form
Authenticated users access /support/contact and fill in a subject and details.
Ticket is persisted
The SupportTicketRepository saves the submission to the support_tickets table, recording the user’s email at the time of submission for audit purposes.
Primary gateway submits the ticket
The CreateSupportTicket action dispatches the ticket to the configured gateway (Email or TeamDynamix). The gateway returns an immutable CreationResult indicating success or failure.
Result is recorded
The repository updates the ticket with the gateway’s response: ticket number, system type, and any error message.
Fallback fires if needed
If the primary gateway is not the email driver and it fails, the system automatically falls back to the MailGateway so the user’s request is still delivered. The fallback_sent_at timestamp is only set if the fallback actually succeeds.
User receives feedback
The controller returns a contextual message:
Default driver
Sends ticket details to the support team mailbox and a confirmation to the requester.
SUP-{id} style referencesTeamDynamix
External ticketing system
Creates tickets directly in TeamDynamix via the REST API.
The email gateway dispatches two queued emails per submission:
SupportTicketMessage) - Contains the full request body, submitter metadata, and a fallback warning when operating in fallback mode.SupportTicketConfirmation) - A user-facing email with the reference number and next-step expectations. No internal details are exposed.The reference prefix is configurable and only applies to the email gateway. External gateways like TeamDynamix return their own native ticket references.
SUPPORT_MAIL_TO=your-team@northwestern.eduSUPPORT_REFERENCE_PREFIX=SUPWhen the email gateway is used as a fallback after a primary gateway failure, the team notification includes a warning banner instructing the team to check Sentry for the corresponding error.
The TeamDynamix (TDX) gateway submits tickets to the TDX REST API using the northwestern-sysdev/tdx-php-sdk package. The SDK handles authentication and token management, and covers a broad surface beyond ticket creation: people/group lookups, service catalog queries, and metadata management. Configuration is published to config/team-dynamix.php.
The gateway resolves metadata IDs (ticket type, form, status, priority, service) through the TeamDynamixCacheRepository, which caches name-to-ID lookups for one week to avoid repeated API round-trips:
SUPPORT_DRIVER=team-dynamix
# TDX SDK credentialsTDX_API_BASE_URL=https://solutions.teamdynamix.com/TDWebApi/TDX_USERNAME=your-service-accountTDX_PASSWORD=your-passwordTDX_TICKET_APP_NAME=your-app-nameTDX_CLIENT_APP_NAME=your-client-app
# TDX ticket defaultsTDX_ASSIGNEE_ID=12345TDX_TICKET_TYPE=DefaultTDX_FORM_TYPE="NU Base Service Request"TDX_TICKET_STATUS=NewTDX_TICKET_PRIORITY="Low (P4)"TDX_SERVICE="My Application"When the primary gateway is not the email driver and it fails, the system dispatches the ticket via the MailGateway in fallback mode. This safety net cannot be disabled, ensuring that user requests are never lost.
The fallback only fires when:
creationError: true)The fallback_sent_at timestamp on the ticket is only set when the fallback actually succeeds. If the fallback also fails (e.g. mail server is down), the timestamp remains null, giving administrators a clear signal that both delivery paths failed.
The SupportTicketResource provides a read-only view of all submitted tickets in the Filament admin panel. It requires the ViewSupportTickets permission and is only visible when SUPPORT_ENABLED=true.
The resource displays a red badge on the navigation item showing the count of tickets where post_error = true, giving administrators immediate visibility into failed submissions.
Tickets are globally searchable by subject, ticket number, requester email, and submitter name/username.
The contact form is available at /support/contact for authenticated users. It collects a subject and details.
In non-production environments, a warning banner is displayed indicating that support is limited and the form may behave differently. This is controlled by:
# Defaults to true for all non-production environmentsSUPPORT_LIMITED_WARNING=falseAdding a new gateway requires three steps: add an enum case, implement the interface, and add configuration.
Add a case to TicketSystem
enum TicketSystem: string implements HasLabel{ case TeamDynamix = 'team-dynamix'; case Mail = 'mail'; case Jira = 'jira'; // New case
public function getLabel(): string { return match ($this) { self::TeamDynamix => 'TeamDynamix', self::Mail => 'Email', self::Jira => 'Jira', }; }
public function gatewayClass(): string { return match ($this) { self::TeamDynamix => TeamDynamixGateway::class, self::Mail => MailGateway::class, self::Jira => JiraGateway::class, }; }}The enum value (e.g. 'jira') is what users set in SUPPORT_DRIVER and what gets stored in the ticketing_system database column.
Create the gateway class
Implement the TicketSystemGateway interface. Your gateway must never throw. Capture errors and return them via CreationResult.
namespace App\Domains\Support\Gateways\Jira;
use App\Domains\Support\Contracts\TicketSystemGateway;use App\Domains\Support\Enums\TicketSystem;use App\Domains\Support\Gateways\CreationResult;use App\Domains\Support\Models\SupportTicket;use Throwable;
class JiraGateway implements TicketSystemGateway{ public function create(SupportTicket $ticket): CreationResult { try { // Submit to Jira API... $issueKey = $this->submitToJira($ticket);
return new CreationResult( ticketSystemType: TicketSystem::Jira, creationError: false, ticketNumber: $issueKey, errorMessage: null, ); } catch (Throwable $e) { report($e);
return new CreationResult( ticketSystemType: TicketSystem::Jira, creationError: true, ticketNumber: null, errorMessage: $e->getMessage(), ); } }}Add configuration
Add a section for your driver in config/support.php and update .env.example:
'jira' => [ 'base_url' => env('JIRA_BASE_URL'), 'project_key' => env('JIRA_PROJECT_KEY'), 'issue_type' => env('JIRA_ISSUE_TYPE', 'Task'),],SUPPORT_DRIVER=jiraJIRA_BASE_URL=https://your-org.atlassian.netJIRA_PROJECT_KEY=SUPThe factory resolves gateways through the enum, so no additional wiring is needed. The automatic email fallback, admin panel, and submission recording all work with your new driver.
If you need a completely custom gateway that doesn’t fit the enum-driven pattern, you can bypass the factory entirely by rebinding the TicketSystemGateway interface in your AppServiceProvider:
use App\Domains\Support\Contracts\TicketSystemGateway;
$this->app->bind(TicketSystemGateway::class, function () { return new MyCustomGateway();});This overrides the binding registered by SupportServiceProvider and gives you full control over gateway resolution.
The contact form uses tiered rate limiting (per-minute, per-hour, and per-day) to allow short bursts while preventing sustained abuse. All limits are per-user.
All rate limiters are defined in config/rate-limiting.php.
SUPPORT_ENABLED false Enable the contact form and submission routes
SUPPORT_LIMITED_WARNING true (non-production) Show limited support warning banner
SUPPORT_DRIVER mail Gateway driver (mail, team-dynamix)
SUPPORT_MAIL_TO your-team@northwestern.edu Support team email address
SUPPORT_REFERENCE_PREFIX SUP Reference prefix for mail gateway (e.g. SUP-47)
TDX_API_BASE_URL Required TDX REST API base URL
TDX_USERNAME Required TDX service account username
TDX_PASSWORD Required TDX service account password
TDX_TICKET_APP_NAME Required TDX application name
TDX_CLIENT_APP_NAME Required TDX client application name
TDX_ASSIGNEE_ID Required TDX group ID for ticket assignment (required for TDX driver)
TDX_TICKET_TYPE Default TDX ticket type name
TDX_FORM_TYPE NU Base Service Request TDX form type name
TDX_TICKET_STATUS New TDX ticket status name
TDX_TICKET_PRIORITY Low (P4) TDX ticket priority name
TDX_SERVICE APP_NAME TDX service name (defaults to application name)