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. Out of the box, it ships with two gateway drivers - Email and TeamDynamix - and an automatic email fallback that ensures user requests are never silently 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 the orchestration from gateway implementationsTicketSystemEnum::gatewayClass() maps each driver to its concrete classCreationResult carries the outcome of every gateway callCreateSupportTicket orchestrates the full 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 clean, 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 internally, and covers a broad surface beyond ticket creation — including 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 automatically dispatches the ticket via the MailGateway in fallback mode. This is a safety net — it cannot be disabled — ensuring that user requests are never silently 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 VIEW_SUPPORT_TICKETS 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 TicketSystemEnum
enum TicketSystemEnum: string implements HasLabel{ case TEAM_DYNAMIX = 'team-dynamix'; case MAIL = 'mail'; case JIRA = 'jira'; // New case
public function getLabel(): string { return match ($this) { self::TEAM_DYNAMIX => 'TeamDynamix', self::MAIL => 'Email', self::JIRA => 'Jira', }; }
public function gatewayClass(): string { return match ($this) { self::TEAM_DYNAMIX => 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\TicketSystemEnum;use App\Domains\Support\Gateway\CreationResult;use App\Domains\Support\Models\SupportTicket;use Exception;
class JiraGateway implements TicketSystemGateway{ public function create(SupportTicket $ticket): CreationResult { try { // Submit to Jira API... $issueKey = $this->submitToJira($ticket);
return new CreationResult( ticketSystemType: TicketSystemEnum::JIRA, creationError: false, ticketNumber: $issueKey, errorMessage: null, ); } catch (Exception $e) { if (app()->bound('sentry')) { resolve('sentry')->captureException($e); }
return new CreationResult( ticketSystemType: TicketSystemEnum::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=SUPThat’s it. The 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 immediately.
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.
| Variable | Default | Description |
|---|---|---|
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 | — | TDX REST API base URL |
TDX_USERNAME | — | TDX service account username |
TDX_PASSWORD | — | TDX service account password |
TDX_TICKET_APP_NAME | — | TDX application name |
TDX_CLIENT_APP_NAME | — | TDX client application name |
TDX_ASSIGNEE_ID | — | 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) |