Relay — Help Desk

A self-hosted PHP/MySQL help desk: customers register, open support tickets and track their full history in a portal, while your team replies, assigns, sets priorities and resolves tickets from a clean agent console — with internal notes, attachments, canned replies, reports and a full audit trail.

1. Introduction

Relay turns a shared support inbox into an organized ticket workflow. It suits agencies, SaaS/plugin sellers, IT service providers and any team that fields customer requests. Key features:

2. Server requirements

Nginx

Apache works out of the box with the included .htaccess. On Nginx, add this to your server block (adjust the PHP-FPM socket):

root /var/www/relay;        # the folder that contains index.php
index index.php;

location / {
    try_files $uri $uri/ /index.php?$query_string;
}
# Never serve internal files
location ~ ^/(config|core|storage|install/sql)/ { deny all; }
location ~ \.(sql|md|log)$ { deny all; }
# Uploaded files are data only
location ^~ /uploads/ { location ~ \.php$ { deny all; } }

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}

3. Installation

  1. Upload the package to your web root (or a subfolder) and unzip.
  2. Visit the site in a browser — you are redirected to /install.
  3. Step 1 — Requirements: confirm every row is green.
  4. Step 2 — Database: enter DB host, name, user, password. On shared hosting create the database in cPanel first.
  5. Step 3 — Account: organization name, admin name/email/password, timezone. Optionally tick demo data.
  6. Step 4 — Install: creates tables, your admin account, and config/config.php.
  7. Step 5 — Done: delete the /install folder, then sign in.
  8. Step 6 — Scheduled tasks (recommended): open System → Scheduled tasks and add the line shown there to your server's cron (cPanel → Cron Jobs), every 5 minutes. Without it, tasks still run while people use the app.
Manual install: copy config/config.sample.php to config/config.php, edit the credentials, then import install/sql/schema.sql into your database.

4. First-run setup

Set up your master data so customers and agents have what they need:

  1. Categories — Billing, Technical, Sales… so tickets can be grouped.
  2. Canned replies — saved responses agents insert with one click.
  3. Users — add agents; admins manage everything.
  4. Settings — logo, brand colour, customer registration, privacy & security, email (SMTP).
Demo logins (when demo data is installed and the admin is created as admin@example.com / admin123): Agent agent@example.com / agent123; Customer john@example.com / customer123.

5. Using the modules

Dashboard

Tickets waiting for an answer, your open tickets, tickets resolved in the last 7 days (compared with the week before) and the average first response time over 30 days; a 14-day chart of new and resolved tickets; your queue, urgent and high-priority tickets, and the latest conversation activity.

Customer portal

Customers visit /portal/ to register or sign in, then open a ticket (subject, category, priority, message, optional attachment). Their tickets are listed as cards with Open / Resolved / All tabs; a ticket with a new answer from support is highlighted. On a ticket they see the conversation, can reply with a file, and can click My issue is solved. They only ever see their own tickets and never see internal notes. Language buttons (when more than one language is installed) let each customer pick their language.

Ticket inbox

Tickets opens on Waiting for us: new tickets and tickets where the customer spoke last, unassigned or assigned to you. Other views: My tickets, Unassigned, All open, Closed and All, each with a count. Waiting tickets are shown in bold, urgent and high-priority tickets have a red or orange edge, and every row shows who replied last and when. Search, filter by priority, category and agent, and sort by last activity or priority. The Tickets menu item shows how many are waiting for you.

Replies, internal notes & attachments

On a ticket, the conversation shows customer messages, agent replies (tinted) and internal notes (yellow). The composer has two tabs: Reply to customer and Internal note (never shown to the customer). Insert a saved reply, attach a file, then Send reply or Send & resolve. Answering an unassigned ticket assigns it to you. The side panel shows the customer with their other tickets, and the status, priority and agent.

Email to ticket

Settings → Email → Incoming email: enter your support mailbox's IMAP details (for Gmail and Microsoft 365 use an app password), click Test connection, switch it on and save. Every few minutes (with the scheduled tasks) Relay reads unread emails: a new email becomes a ticket; a reply whose subject contains the ticket reference, such as [T-2026-000123], is added to that ticket (from the customer, or from an agent answering by email). Text below ## Reply above this line ## and quoted history (“On … wrote:”) is removed, attachments are kept, and auto-replies, bounces and duplicates are skipped. Emails are marked as read (or deleted, if you choose). All outgoing ticket emails carry the reference in the subject so customers can simply reply. Relay has its own IMAP client, so the PHP imap extension is not needed.

Departments and automatic assignment

Departments are teams of agents. Link a category to a department (Categories → edit) and give a department its own email address; new tickets in that category, or sent to that address, go to the department. Choose how they are assigned: Off (agents pick them), Round-robin (agents take turns) or Least busy (the agent with the fewest open tickets). New-ticket emails go to the assigned agent, else the department's agents, else everyone.

SLA (service level) targets

Settings → SLA sets, per priority, how many hours until the first answer and until resolution (defaults: urgent 1 h / 8 h, high 4 h / 24 h, normal 8 h / 48 h, low 24 h / 120 h; 24/7 clock). Each ticket shows a countdown (“Reply due in 2h 10m”, “Resolution overdue 3h”), the inbox has an Overdue view, overdue tickets appear in the bell, and the assigned agent (or the admins) get one email when a target is missed. Changing a ticket's priority recalculates its targets.

Tags and custom fields

Type tags on any ticket (comma-separated; new tags are created automatically) and filter the inbox by tag; rename, recolour or delete them on the Tags page. Custom fields add questions to the ticket form (text, number, date, list, checkbox), for all tickets or one category, on the portal and/or for agents; answers appear in the ticket's Additional details.

Saved replies

Insert a saved reply from the composer. Placeholders are filled in for the ticket: {customer_name}, {customer_first_name}, {ticket_ref}, {ticket_subject}, {agent_name}, {agent_first_name}, {company}.

Customer satisfaction

When a ticket is resolved, the customer gets a “How did we do?” email with one-click stars (1–5) and can add a comment; resolved tickets in the portal show the same rating box. The rating appears on the ticket and the dashboard shows the satisfaction score (share of 4–5 star ratings, last 30 days). Turn it off in Settings → Portal & notifications.

Knowledge base and Help Center

Knowledge base (agents) is where you write help articles in categories, as draft or published, with simple formatting (headings, lists, bold, links) and a live preview. Published articles appear in the public Help Center at /help/ with search, categories, popular and related articles and a “Was this article helpful?” vote. While a customer types the subject of a new ticket, matching articles are suggested — many questions are answered before a ticket is opened.

Guest tickets and the website widget

Settings → Widget & guests. With Guest tickets on, anyone can open a ticket at /public/new without an account. Every ticket has a private link (sent in the confirmation and reply emails) where the customer reads the conversation, replies and rates it — no sign-in needed. With Website widget on, paste the one-line snippet shown there into your website: a round Help button opens a panel with help-article search and a contact form. Choose the greeting, button text and side, and optionally list the only websites allowed to show it. The widget form is protected by a signed, time-limited key, a honeypot and a limit of 10 tickets per network address per hour.

Bulk actions, merge and macros

Collision warning

When two agents open the same ticket, each sees “Sara is also viewing” — or “Sara is replying to this ticket” while she types — and a notice when a new message arrives, with a link to show it. The page checks every few seconds.

Insights

Insights (Reports → Insights) shows, for 7 days, 30 days, 90 days or 12 months: new and resolved tickets, average first-response and resolution time, the share of SLA targets met, satisfaction, a chart of new vs resolved tickets, channels (portal, email, widget, agent) and categories, and a sortable table per agent (resolved, replies, first response, time to resolve, rating, open now).

Customers

Customers lists everyone with a portal account or a ticket, with ticket counts and last activity. A customer's page shows all their tickets, lets staff disable or enable the portal account, and starts a new ticket for them. Administrators can Export data (a ZIP with data.json and the attachments; internal notes are not included) or Erase a customer: the account is anonymized, every message and attachment on their tickets is removed and open tickets are closed, while the ticket rows stay for reports. Both are recorded in the audit log.

Status lifecycle

Tickets move open → pending → resolved → closed. "Pending" means awaiting the customer. A customer reply automatically reopens a pending, resolved or closed ticket.

Agent-created tickets

Use New ticket to log a request on a customer's behalf (e.g. a phone call). Enter the customer email — an account is found or created automatically.

Reports & exports

Reports filters the full ticket history by date range, status, category, agent and priority, with CSV and PDF export of the current filter.

Public ticket tracking

Anyone with a reference code and the matching email can check a ticket's status at /public/track without logging in.

Two-factor authentication

Staff can turn on two-factor sign-in in My Account (click your name at the bottom of the menu): scan the QR code with Google Authenticator, Microsoft Authenticator, 1Password or a similar app and enter the 6-digit code. If someone loses their phone, an administrator opens Users and clicks Reset two-factor (shield icon) on that user.

Automatic first reply

Settings → Portal & notifications → Automatic first reply. When a customer opens a ticket (portal, email or widget), a short "we received your request" message is added to the conversation and emailed to them. Edit the text with the placeholders {customer_first_name}, {customer_name}, {ticket_ref}, {ticket_subject}, {company}. It is not a human answer: the ticket still waits for an agent and SLA timers keep running.

AI assist

Optional, off by default. Settings → AI: turn it on, pick a provider (OpenAI, Google Gemini or any OpenAI-compatible API such as OpenRouter, Groq, DeepSeek, Mistral or a local Ollama server, by entering its base URL), optionally a model (leave blank for the recommended one) and your API key, then click Test connection. Keys: platform.openai.com or aistudio.google.com; usage is billed to your own account by the provider.

On a ticket, agents get an AI assist menu in the reply box (Draft a reply from the conversation and matching help articles; Fix grammar, Friendlier, More formal, Shorter; Translate to the customer's language), a Summarise link above the conversation and Translate on each customer message. Results go into the reply box (with Undo) — nothing is ever sent without the agent. Only that ticket's text and matching help articles are sent to the provider; every use is recorded in the audit log. Requires the cURL PHP extension.

Ticket page tools

Hide panel gives the conversation the full width; each side card (Customer, Properties, Tags, Details) folds from its title; long messages show Show more; long threads fold the middle messages. Your choices are remembered. Messages support simple formatting (bold, italic, lists, links, code) with a Preview button, and Ctrl/⌘ + Enter sends.

Installable app (phone, tablet, desktop)

Relay can be installed like an app. In Chrome or Edge a download icon appears in the top bar (or use the browser menu → Install app); on Android choose Add to Home screen, on iPhone/iPad tap Share → Add to Home Screen. The app opens full screen with your name and colour, and shows a small offline page when the connection drops. HTTPS is required (it works on localhost for testing).

App icon: Settings → General → App icon: upload a square image (512×512 or larger). Without one, the icon is made from your logo, and without a logo the default icon is used. Changing it updates the browser tab, home-screen and installed-app icon.

Notifications (bell)

The bell shows what needs attention right now. Opening it does not mark anything as read: an alert is marked read when you click it, or use Mark all as read. The badge counts unread alerts only, and an alert becomes unread again when it changes (for example the customer replies again).

Reference numbers

Settings → General → Reference style: Numbered (e.g. 2026-000123, the default) or Random (e.g. 7KQ2-9XMD), which does not reveal how many records you have. It applies to new records only; both styles keep working everywhere.

Light and dark mode

The moon / sun button in the top bar (and in the customer portal header) switches between light and dark mode for that browser. Until someone chooses, the app follows the device's own setting.

6. Settings reference

SettingWhat it does
Organization name / logoBranding on the console, portal and emails.
Confirm new tickets / ratings / Help CenterPortal & notifications tab. Send the customer their reference when they open a ticket; ask for a rating when a ticket is resolved; show the public Help Center.
SLA targetsSLA tab. First response and resolution hours per priority (see above).
Incoming emailEmail tab. IMAP host, port, security, username, password, folder, and whether to delete emails after import.
Brand colourButtons, links, the sidebar and the customer portal follow this colour (default teal).
Customer personal data (days)Privacy & security tab. Messages, attachments and contact details on tickets closed longer than this are removed or anonymized, daily. 0 = keep indefinitely.
Audit log (days)Privacy & security tab. Older audit entries are deleted daily (default 365; 0 = keep).
Sign out after inactivity / password rulesPrivacy & security tab. Session timeout (default 120 minutes), minimum password length and an optional letters-and-numbers rule for agents and customers.
Rows per pagePage size for all lists.
Date format / timezoneHow dates display across the app.
LanguageInterface language. English and Spanish are included; see Customization to add one.
Allow customer registrationLet new customers create portal accounts. Off = invite/agent-created only.
Notify agents of new ticketsEmail all staff when a ticket is opened (needs SMTP).
Notify customer of agent repliesEmail the customer when an agent replies (needs SMTP).
Notify agent of customer repliesEmail the assigned agent when the customer replies (needs SMTP).
CAPTCHASpam protection on customer registration, ticket tracking and forgot-password forms: a built-in math question (default, works offline), Cloudflare Turnstile or Google reCAPTCHA v2 (add the site key and secret key). Optionally also on the sign-in pages.
Email (SMTP)Host/port/user/password/from + a "send test email" button. Off by default.

System (Administration → System)

TabWhat it does
HealthChecks PHP version and extensions, memory and upload limits, disk space, HTTPS, environment, email, cron, database size and schema version, and that the upload and storage folders are writable. Copy system info copies a plain-text summary for support requests.
BackupsBack up now saves the database as a compressed .sql.gz (pure PHP, no mysqldump needed) and, optionally, the uploads folder as a ZIP, in storage/backups (blocked from the web). Download either part, delete, or Restore… (type RESTORE to confirm; a safety copy of the current database is made first). Upload and restore accepts a .sql.gz from another Relay server. Automatic daily backups run with the scheduled tasks, and a backup is made before every database update.
Scheduled tasksOne cron job (php /path/cron.php, or the URL with its secret key) runs email to ticket (every minute when cron runs that often), email retries and SLA alerts (every 5 minutes) and the retention purge, daily backup and temporary file clean-up (daily). Without cron, due tasks run after normal page requests.
Email logEvery email Relay sends, with status and error. Failed emails are retried automatically (up to 3 attempts); preview and resend from the list. Kept for 90 days.
Error logThe last 200 lines of storage/error.log, with download and clear.
MaintenanceShows a “back soon” page (HTTP 503) to everyone except administrators. It turns on automatically while a database update runs.

Security: after 5 wrong passwords an account is locked for 15 minutes, and failed and locked sign-ins appear in the audit log. Each user can Sign out other devices from My account; changing or resetting a password signs out every other session.

7. Roles & permissions

CapabilityAdminAgentCustomer
Manage users / categories / canned replies / settings✅❌❌
View & work all tickets (reply, assign, status, notes)✅✅❌
Internal notes✅✅❌
Open a ticket✅ (on behalf)✅ (on behalf)✅ (own)
View ticket history✅ (all)✅ (all)✅ (own only)
Reports & CSV export✅✅❌
View audit log✅❌❌

Customers live in a separate table with their own login and can never reach any staff/admin route.

8. Customization

9. FAQ & troubleshooting

Blank page after install

Ensure mod_rewrite is enabled and config/config.php exists. Set APP_ENV to development temporarily to see errors.

Mail not sending

Email is off by default. Enable it in Settings → Email, save, then use "Send test". Port 465 uses SMTPS, 587 uses STARTTLS. Use a real mailbox on your own domain (e.g. support@yourdomain.com) as the sender. Hosts limit failed deliveries per hour: if agents or customers have invalid addresses, bounces can make the mail server refuse further mail for a while — keep user emails real.

Upload or permission errors

Make /uploads and /storage, and every folder inside them, writable by the web server (for example chmod -R 775 uploads storage). The installer’s first step lists any folder that is not writable.

Installer says "already installed"

Delete config/config.php and install/install.lock to re-run (this does not drop your database).

Attachment upload rejected

Attachments are limited by type (images and common documents) and size (10 MB). The real file type is verified server-side, so renaming a file will not bypass the check.

A customer can't see internal notes — is that a bug?

No. Internal notes are agent-only by design and are never shown in the portal or in any email.

Updating to a new version

Make a backup in System → Backups and download it. Upload the new files over the old ones, keeping config/config.php and the uploads folder. Sign in as an administrator: if the database needs changes, a notice with an Upgrade now button appears at the top of the page.

Running a public demo

Add define('DEMO_MODE', true); to config/config.php. Everything stays editable, and the database and uploads are rebuilt from the demo data every DEMO_RESET_MINUTES (default 60). SMTP and CAPTCHA settings, password changes and the demo sign-in accounts are locked. Never turn this on for a real installation: each reset erases all data.

10. Changelog

1.0 — Initial release.

11. Credits & third-party licenses

Bundled open-source libraries (see /licenses): Bootstrap 5, Bootstrap Icons, jQuery, Chart.js, Dompdf, PHPMailer, QR Code Generator, Inter font (SIL Open Font License). All assets are bundled locally — the app needs no internet connection (only the optional Turnstile / reCAPTCHA providers load from their own servers).

12. Support

For help, contact us through your CodeCanyon item page. Please include your PHP/MySQL versions and any error message.