Skip to main content

Documentation

No results found.
Features

Support Requests

Every dashboard page carries a support button in the floating cluster at the top-right — third in the row, after the dark-mode toggle and the exit-to-website link. Clicking it opens a modal where the user picks what kind of request this is...

Every dashboard page carries a support button in the floating cluster at the top-right — third in the row, after the dark-mode toggle and the exit-to-website link. Clicking it opens a modal where the user picks what kind of request this is (report a bug, suggest a feature, ask for help), types a subject and the details, and sends. It becomes a ticket on the support desk of the server that licenses the site, with the site's install details attached for the support team only, and the person who sent it gets the thread link in the modal and every reply by email. Nothing is emailed to a support address.


The problem

When something looks wrong on a site, the person who notices it is standing in the dashboard — not in their email client. Asking them to switch context, remember the support address, describe which site they're on, and dig up their CMS version means most small reports never get sent at all. The ones that do arrive usually open with a round-trip: which install is this? what version are you on?

The fix

A support form where the problem is, pre-loaded with the answers to the triage questions. The user types two fields; the install supplies the rest.

Piece Where it lives
Modal + form + send SupportRequest (Livewire), view
Floating trigger button sidebar-locale-appearance.blade.php — the top-right cluster, its only trigger
Where it goes, with which key SupportDesk
The desk's intake SupportRequestController — POST api/membership/support-request (and the api/v1/ twin)
Off switch config('cms.support_requests') (env CMS_SUPPORT_REQUESTS), default on

The three request types

The type is a required choice, rendered as three cards. It sets the ticket subject's prefix so requests sort themselves on the desk.

Type For Subject prefix
Report a bug Something is broken or not behaving the way it should [Bug]
Suggest a feature An idea for something the CMS should be able to do [Feature request]
Ask for help Stuck and needs a hand [Support]

The full subject is [<type>] <install host>: <subject>, so the ticket list shows which site each request came from without opening it. A host that isn't one of the sender's registered installs is marked (unverified).

What gets sent

Alongside the typed subject and details, the request carries a short diagnostics table so support doesn't have to ask. It lands in a staff-only internal note on the ticket, never in the client's own thread or their emails:

  • Site name and install URL
  • The dashboard page the request was sent from
  • CMS version, PHP version, Laravel version
  • The last four characters of the license key, and of the fleet key on a fleet client — enough to match the install to its Member record. A fleet client's license key is its head's, shared by the whole fleet, so the fleet key's tail is the one that names the site. No key is ever sent whole in the body.
  • The install ID
  • The sender's role, and the send time in the site's timezone

The labels are in the dashboard's base language (English), whatever language the sender uses: the desk's staff read them. The note also records which member's key filed it and whether the claimed site and install ID are registered to that member.

The modal states plainly what is attached ("Your name, email, site address, and CMS version are included so we can help faster") — nothing is collected silently.

Attaching recent server errors

Bug reports and help requests offer an Attach recent server errors checkbox, checked by default. It sends the five most recent entries from this install's own error log (within the last 7 days) — each with level, exception class, message, file:line, the page URL and method, and the occurrence count — plus the full stack trace of the newest one.

It never sends visitor data. The ip, user_agent, user_id, and request context columns are all omitted: they describe who was browsing, not what broke. The errors and the trace go only into the staff note.

Two deliberate constraints:

  • Admin-only. The checkbox is gated to Admin and above, matching the role:admin route on the Error Log page. A Manager cannot read these entries in the dashboard, so they cannot send them out either — and unchecking is not required, a Manager's report simply carries no errors even if the property is set.
  • Hidden for feature suggestions. An idea for a new capability has no error to explain, so the checkbox only appears for bug and help requests. (The type picker is wire:model.live so it appears and disappears as the type changes.)

The gate deliberately does not check whether any errors exist. This modal renders on every dashboard page and error_logs.last_seen_at carries no index, so a per-page-view existence check would buy a slightly more precise checkbox with a table scan. Attaching when nothing recent has errored simply attaches nothing, and the note omits the section.

Why this is not redundant with error telemetry

Installs already stream a scrubbed error digest to the mothership on the 6-hourly license check-in (see Error Telemetry). That feed answers "is the fleet erroring?" — up to 20 deduplicated signatures, with no stack traces, no URLs, and no request context, arriving up to six hours late, and switched off entirely by any install that opts out of telemetry.share_errors.

A bug report needs the opposite shape: full detail about a handful of errors, right now, tied to a human explaining what they were doing. The attach option is the consent gate for that richer local data, and it works even on an install that has telemetry turned off.

Delivery

Where it goes. The request is filed with the support desk of the server that licenses the install (SupportDesk::destination()):

  • A fleet client (it reports to an agency fleet head) files with that head, using its fleet key, the one credential that names this site alone. Its license key is never used for this, because it is the head's own key, shared by every site in the fleet. A white-label agency's clients therefore reach that agency, never WebProCMS.
  • Anything else files with the license server, using its license key. It's the same server the check-in goes to (MembershipClient::licenseServerUrl()): fixed config, which only an owner install (author mode, the license server itself) may repoint.
  • The endpoint is the /check URL's support-request sibling, the same derivation the update health report uses. The fleet URL is an admin Setting with a credential attached, so it is sent through OutboundHttp::guarded(). A private or unresolvable address is refused before anything is sent.

On the desk (SupportRequestController):

  • It answers only on a server that issues license keys and has Support Tickets switched on; anywhere else it is a 404.
  • The bearer key names the member, which is always taken from the key, never from the payload. An unknown key gets a 403.
  • It opens a ticket for the person who clicked, with team notice, auto-assignment and CRM. The staff-only diagnostics note is written in the same transaction as the ticket, so it is there before anyone is notified. It skips the content spam check, because the caller is authenticated.
  • No automatic confirmation email goes to the requester. The address is whatever the caller sent, and an automatic email to it, quoting the caller's own text, would let any key holder mail anyone from the desk's server. The widget shows the thread link instead, and staff replies still reach the requester by email.
  • A member may file 10 an hour, on top of the route's per-IP throttle; every site on one server shares an IP.
  • Each request carries a request_id, stored on the ticket's first message (install-request:{member}:{id}) in the ticket's own transaction, before any email goes out. A retry with the same id returns the same ticket, however long the first attempt's sends took. The widget keeps the id across failed attempts of the same request and mints a new one when the type, subject or message changes, or after a success. That way, editing after a timed-out attempt can't be swallowed by the first one.
  • The response is {ok, ticket, url}, and the modal shows Ticket #N opened with a View your ticket link.

When it can't be filed, nothing throws and the typed text stays in the form. The modal says which case it is (failure on the component), because only some of them clear up with a retry:

  • no desk there (404): "not available for this site yet"
  • the desk doesn't recognise the key (403): points at Settings → Updates & License
  • the per-member limit (429): try again later
  • a rejected payload (422)
  • a server error or network failure: try again in a few minutes

The desk's status and error code are logged. There is no email fallback. The subject and message are trimmed before they are validated, so input that passes in the modal also passes at the desk. The request posts synchronously, because production installs run no queue worker.

Each user may send five requests per fifteen minutes on the install's side. A sixth is turned away with a message asking them to wait.

Where the modal lives

The floating button is the modal's only trigger; the old sidebar Get Support item was removed. The modal is mounted outside the floating cluster (which is hidden below lg), so the dialog isn't nested inside a display:none ancestor.

White-label installs

An agency's clients already file with the agency's own head (see Delivery), so no setting is needed to route them there. If that head has Support Tickets off, they see "Support requests aren't available for this site yet". CMS_SUPPORT_REQUESTS=false removes the widget entirely: the button disappears and the modal is never rendered. The widget also hides itself when there is nowhere to send, meaning no license key and no fleet head.

CMS_SUPPORT_EMAIL and the support-request email were retired when requests became tickets; no install set the variable.