Build and Deploy a Customer Support Ticket System With Claude Code
Build a real customer support system with ticket submission, private customer replies and a protected staff dashboard, then deploy it to Hostinger.
Roadmap & Resources
Plan the Support System
Plan the Ticket Workflow
Open your project in Claude Code, fill in the three fields, and use this prompt. It only plans — nothing changes yet.
Plan the Customer Support System
Tell Claude what the business supports, what customers should provide, and what staff needs to manage before anything is built.
I want to build a lightweight customer support ticket system with Claude Code. Business: {{BUSINESS_TYPE}} Customer ticket information: {{TICKET_INFORMATION}} Support workflow: {{SUPPORT_WORKFLOW}} Do NOT build anything yet. First inspect the existing project. If this is an empty or new project, inspect the available stack and configuration and propose the smallest appropriate implementation. 1. CUSTOMER WORKFLOW Plan: 1. The customer opens Support 2. The customer completes the ticket form 3. The customer submits it 4. The customer gets a confirmation and a ticket reference 5. The customer gets a private ticket URL 6. The customer opens that URL later 7. The customer reads staff replies 8. The customer sends another reply if needed 9. The customer sees the current status Do not require customer authentication. 2. STAFF WORKFLOW Plan a proper protected Support Dashboard. Staff must NOT manage support by editing Supabase rows. Inside the application, staff should be able to: - sign in - see new and open tickets - open a ticket - read the whole conversation - reply - change the status - resolve the ticket 3. TICKET DATA Plan the smallest useful model. The likely concepts are a TICKET, a TICKET MESSAGE / REPLY, and STAFF access. Do not add entities the workflow does not need. 4. CONVERSATION MODEL A ticket holds a chronological conversation. Every message records whether it came from the CUSTOMER or from SUPPORT. Do not trust a browser-supplied sender type as authority. Staff identity must come from trusted authentication and authorization. 5. TICKET STATUS Use a small constrained status model: NEW → IN PROGRESS → WAITING FOR CUSTOMER → RESOLVED Add CLOSED only if it is genuinely useful for this business. No free-text statuses. 6. PRIVATE TICKET ACCESS Customers have no account, so plan a secure private ticket-access mechanism: a high-entropy unguessable access token, or another pattern already safe in this project. A predictable ticket id must never be the only authorization — /support/ticket/104 is not access control. The customer may still SEE a friendly reference such as #104 in the interface, but that display number is not the security boundary. One token must reach exactly one ticket, and private customer data must not sit in the URL itself. 7. SECURITY MODEL A PUBLIC CUSTOMER may: - submit a ticket - open only the ticket their valid token belongs to - read the customer-visible conversation and status - add a customer reply to that ticket A PUBLIC CUSTOMER must NOT: - list all tickets - read another ticket - impersonate staff - set a staff-only status - read internal staff data - reach the Support Dashboard AUTHORIZED STAFF may: - authenticate - read and manage tickets - reply as staff - change statuses - resolve tickets Being signed in is not the same as being support staff — plan a trusted staff check. Never put a Supabase Secret or service-role key in browser code. 8. EXCLUDED FROM VERSION 1 Do not plan: email notifications, SMS, live chat, an AI chatbot, AI reply generation, a knowledge base, file attachments, screenshot uploads, SLAs, priority automation, ticket assignment, multiple support teams, canned replies, internal staff chat, customer accounts, a customer portal, billing, Stripe, CRM features, analytics dashboards, satisfaction surveys, WhatsApp, or a Zendesk or Intercom integration. Return: SUPPORT SYSTEM PLAN Public pages: Staff pages: Ticket fields: Ticket statuses: Conversation model: Customer private-access model: Staff authentication: Public permissions: Staff permissions: Features deliberately excluded: Then return exactly one: READY TO BUILD or: NEEDS A DECISION Do not modify anything yet.
What type of business is this support system for?
SaaS app, agency, ecommerce store, hosting company, local service business...
What should customers provide when submitting a ticket?
Name, email, subject, problem description, order number...
How should staff handle tickets?
Review the issue, reply, wait for customer, resolve...
Review the support workflow before continuing. Keep version 1 focused on submit → reply → resolve.
Confirm the MVP
Make sure the approved plan includes exactly this:
Public ticket submission
Private ticket link
Staff dashboard
Two-way replies
Ticket status
Resolve workflow
No unnecessary help-desk features
Set Up Supabase
Create or Reuse Supabase
If this project is already connected to the right Supabase project, reuse it — don't create a second one.
Open Supabase
https://supabase.link/r4gdwhi
Create a new project if you don't have one Open the project's Connect panel Copy the Project URL and the Publishable key Ask Claude Code which environment variable names this project uses, then add both values locally
IMPORTANT
The browser app only needs the Project URL and the Publishable key. A Secret key belongs only in a trusted server environment, and only where one is genuinely needed — never in browser code and never committed to GitHub.
Create the Ticket Data
Use this prompt in the same Claude Code session.
Create the Support Database
Create the smallest Supabase structure for tickets, replies and protected staff access.
Using the approved support plan, create the minimum Supabase data model and access rules. Reuse correct existing structures where appropriate. Do not duplicate existing staff or admin authentication infrastructure. Never print secret values. 1. TICKETS Use only the approved fields. Typical fields where useful: id, a display or reference number, customer name, customer email or contact, subject, status, the secure access-token representation, created_at, updated_at. Do not collect unnecessary personal information. 2. TICKET MESSAGES Create the minimum message structure. Possible fields: the ticket id, the sender type, the authenticated staff id where relevant, the message body, created_at. The sender type must be decided on the trusted path, never taken from whatever the browser sent. 3. STATUS Use constrained values rather than free text: new in_progress waiting_for_customer resolved Add closed only if the approved plan asked for it. Public customers must never be able to set an arbitrary ticket status. 4. PUBLIC SUBMISSION Public visitors must be able to create a new ticket safely. Do NOT grant broad anonymous read or write access on these tables just to make the form work — use the smallest trusted server or database path the existing architecture supports. The customer may supply only the approved submission fields. Trusted logic sets the ticket identity, the initial status, the access token, the timestamps and every internal field. 5. PRIVATE TICKET TOKEN Generate the token on a trusted path with sufficient entropy. The browser must never choose it. If the established architecture cleanly supports hashed-token verification, avoid storing the raw token unnecessarily — but do not overcomplicate the implementation to demonstrate cryptography. The outcome that matters is that the token cannot realistically be guessed and that one token maps only to its intended ticket. 6. PUBLIC TICKET READ Do not make the tickets table broadly public-readable. A valid customer token should return only that ticket's customer-visible fields, its customer-visible conversation and its status. Do not expose internal staff data, private staff fields, database metadata or any other ticket. 7. CUSTOMER REPLY A valid ticket token may add a CUSTOMER message to its own ticket, and nothing else. It must not be able to send a staff message, modify another ticket, change arbitrary ticket fields or read messages from another ticket. 8. STAFF AUTHORIZATION Use real authenticated staff authorization. Do not treat every signed-in Supabase user as support staff. Do not rely on hidden UI, localStorage, frontend role state or profile metadata the client can edit about itself. The database or server rules must protect staff actions. Reuse the closest admin or staff authorization pattern this project already uses. Tell me how to grant the FIRST staff account safely — for example one reviewed SQL statement run once in the Supabase SQL Editor — never through self-service sign-up. 9. STAFF REPLY A trusted authenticated path creates staff replies, and the staff identity comes from the authenticated session. Never trust a staff_id or a sender type supplied by an arbitrary public request. Give me: 1. Tables reused 2. Tables created 3. The public ticket-submission model 4. The private ticket-access model 5. The customer reply model 6. The staff authorization model 7. The staff reply model 8. The status model 9. ONE reviewed SQL / migration block where appropriate 10. Any server route, RPC or Edge Function required 11. Any blocker Do not use destructive DROP statements. If a table with the same name already exists, warn me instead of replacing it. Do not execute destructive database changes automatically, and do not build the UI yet.
Review the database plan and SQL, then apply it with your project's Supabase workflow — usually the SQL Editor in your Supabase project. Create your own staff account and grant it support access the way the report describes.
The ticket and message tables exist in Supabase
My staff account has support access
Build Ticket Submission
Build the Support Form
Build the Customer Support Form
Build the public ticket form and create a real support ticket without exposing the private support database.
Build the customer-facing ticket-submission flow from the approved support plan, using the Supabase structure and trusted submission path we just created. Reuse the current website design and components. Do not redesign unrelated pages. 1. ROUTE Reuse an existing support or contact route if one genuinely fits, otherwise create a clear route such as /support. Choose what suits this project, and do not leave two competing contact experiences behind. 2. FORM Use only the approved ticket fields — typically a name, an email, a subject and a description of the problem, plus whatever this business approved. Each gets a clear label, an appropriate input type and useful validation. 3. VALIDATION Validate the important fields on the trusted submission path, not only in the browser. Apply reasonable limits to long text. Never expose raw Supabase or database errors. 4. SUBMIT On submit: create a real ticket, let trusted logic assign the initial NEW status, let trusted logic generate the ticket access token, and record the customer's problem description as the first message in the conversation so the thread starts correctly. Prevent accidental double submission where practical. The customer must not be able to set any internal field. 5. SUCCESS Show a clear confirmation: Ticket Created, the ticket reference (for example #104), and a clear View My Ticket action that opens the private ticket link. Do not promise an email — email automation is outside this guide — and never show a fake success. 6. THE PRIVATE LINK IS THE ONLY WAY BACK Because there is no email yet, the private ticket URL is the customer's only route back to their ticket. Do NOT build a confirmation the customer can leave and lose the link forever. Use the smallest appropriate UX so they can deliberately open it now or copy and save it, and say plainly that this link is what they need to return to the ticket. Do not put the private token into analytics events, page titles or client-side logging. 7. FAILURE If ticket creation fails: preserve the entered fields where practical, show a useful generic error, allow a retry, and never display raw database details. 8. RESPONSIVE Keep the form usable on mobile. Do not add complex visual effects. Use the Supabase Project URL and Publishable key through this project's environment-variable pattern. No Secret or service-role key in browser code. When finished, report: - the public route and components - the ticket fields - where validation happens - how the reference and the private link are produced - how the customer keeps the private link - success and error behaviour
IMPORTANT
There is no email in this version, so the private ticket link is the customer's only way back to their ticket. The confirmation has to let them open or copy it on purpose — a link that disappears when they close the page takes the whole conversation with it.
Test Ticket Submission
Run the app and submit one ticket with test information:
Open Support
Submit one test ticket
The confirmation appears
The ticket reference appears
The private ticket link works
The ticket starts as New
A public visitor cannot list other tickets
Checking the stored ticket in Supabase is fine while you test. Support staff will use the dashboard you build next.
Build the Support Dashboard
Build the Support Dashboard
Build the Support Dashboard
Create the protected dashboard support staff will use to review, reply to and resolve customer tickets.
Build the staff-facing Support Dashboard, using the approved Supabase staff authorization model. This dashboard is the business's daily operational interface. Do NOT tell staff to manage support from the Supabase dashboard. 1. STAFF LOGIN Use Supabase Auth. If this project already has trusted staff authentication, reuse it. If not, add the smallest secure staff sign-in flow required. Do not create customer accounts. 2. AUTHORIZATION After sign-in, verify the account is actually support staff. A signed-in account without support access sees a clear "no access" state. Do not rely on hidden navigation, localStorage, frontend role state or editable browser data. The database rules must enforce staff permissions. 3. DASHBOARD Prioritise operational information — useful groups or filters across New, In Progress, Waiting for Customer and Resolved. Do not add fake analytics charts. 4. TICKET LIST Show concise useful information: the ticket number, the subject, the customer, the status, when it was last updated, and the created time where that helps. Make new tickets easy to notice, and put the work that needs attention first. 5. TICKET DETAIL Show the ticket reference, the customer information, the subject, the chronological conversation and the current status. Make it visually obvious which messages are from the Customer and which are from Support. Use the existing design system rather than inventing a new one. 6. STATUS ACTIONS Let staff move a ticket New → In Progress → Waiting for Customer → Resolved using controlled actions or select values. Never allow arbitrary editable status text, and the database must reject a status change from anyone who is not authorized staff. 7. RESPONSIVE This must stay usable on desktop, tablet and phone. Do not build desktop-only support tooling. 8. SECURITY Support routes and database operations must be protected by real authorization. A public visitor, or a signed-in account that is not support staff, must not be able to list tickets, read customer details, read the conversation or change a status. Do not change the public ticket form. When finished, report: - the staff routes - how sign-in and the staff check work - where staff permissions are enforced in the database - the ticket list, ticket detail and status behaviour
Then sign in with your staff account and check:
Your test ticket appears in the dashboard
Open it and read the customer's first message
Move it New → In Progress
A signed-out browser cannot open the dashboard
Add Staff Replies
Add Support Replies
Let authorized staff reply inside a ticket and update its support status without exposing admin actions publicly.
Add staff reply behaviour to the protected ticket detail, using the approved staff reply model. 1. REPLY An authorized staff member can write a reply, submit it, and see it appear in the conversation immediately. Create the reply on the trusted path, with the staff identity taken from the authenticated session. Never trust the browser to declare itself staff, and never accept a sender type or a staff id from a public request. 2. STATUS AFTER REPLY Staff may choose Waiting for Customer after sending a reply. Do not force every reply to change the status automatically unless that matches the approved workflow — leave the decision with the person answering. 3. RESOLVE Let an authorized staff member mark a ticket Resolved. Do not delete the conversation: the support history stays readable afterwards. 4. CUSTOMER-FACING DATA Do not expose internal staff authorization fields, staff email addresses or role data in anything the customer-facing ticket response returns. 5. ERRORS If sending a reply fails, do not fake the reply in the permanent UI and do not silently drop it. Show a useful error and preserve the typed text where practical. When finished, report: - where the reply action lives - how the staff identity is established - what the status does after a reply - what Resolved changes - where reply write permissions are enforced
Then check:
Send a staff reply and see it in the conversation
The reply is labelled Support, not Customer
Set the status to Waiting for Customer
IMPORTANT
A browser claiming to be staff proves nothing. The staff label on a reply must come from the authenticated session on a trusted path — if a public request can send a message marked Support, anyone can answer your customers in your name.
Build and Test Ticket Replies
Build the Private Ticket Page
Build the Customer Ticket Page
Create the private customer ticket view where the customer can see support replies, status and send another message.
Build the private customer ticket page using the approved token-access model. 1. ACCESS Read the secure access token from the route and validate it through the trusted backend or database path. Do not broadly query the tickets table, do not treat the friendly ticket number as authorization, and do not expose any other ticket. 2. DISPLAY Show the ticket reference, the subject, the current status and the chronological conversation. Visually distinguish Customer messages from Support messages. Keep it professional and mobile-friendly — most customers will open this link on a phone. 3. CUSTOMER REPLY Let the customer add another reply to this ticket only, through the validated private access. The customer must not be able to choose the sender type, another ticket id, a staff identity or an arbitrary status. 4. STATUS Show the current status. If staff set Waiting for Customer, a customer reply may move the ticket back to In Progress — but only through trusted logic, never by the customer setting a status directly. Choose the smallest sensible behaviour. 5. RESOLVED TICKETS For a resolved ticket, keep the conversation readable. Decide from the approved MVP whether a customer reply reopens the ticket through trusted logic, or whether replying is simply disabled after resolution. Prefer the simplest behaviour, and do not build a complex reopen policy. 6. INVALID TOKEN For a missing, invalid or expired token, show one generic state: "This ticket link is invalid or no longer available." Do not reveal whether another ticket exists, and do not leak anything about other records. When finished, report: - the private ticket route - how the token is validated - which fields the customer can see - how a customer reply is recorded - what happens to the status after a customer reply - what an invalid link shows
IMPORTANT
The friendly number is for humans; the token is the security. A ticket reachable at /support/ticket/104 lets anyone count up through every customer's support conversation, so the private link must carry an unguessable token instead.
Verify the Full Ticket Workflow
Open the private ticket link in a signed-out or incognito window and the Support Dashboard in another, then run one full conversation.
Customer: the private link opens the ticket
Customer: the staff reply is visible
Customer: send another reply
Staff: the customer reply appears in the dashboard
Staff: mark the ticket Resolved
Customer: the conversation is still readable
Change a few characters in the ticket URL — nothing is exposed
Verify the Support Ticket System
Test ticket submission, staff replies, private customer access and support authorization before deployment.
Perform a focused verification of this customer support ticket system. Use test data only. Do not add features. Never print secret values. 1. PUBLIC SUBMISSION Verify: - a public visitor can submit a valid ticket - trusted logic assigns the initial New status - a public visitor cannot set staff-only fields - a public visitor cannot list or read all tickets 2. PRIVATE ACCESS Verify: - a valid ticket token opens the intended ticket - an invalid or random token exposes nothing - changing the friendly ticket number does not grant access - the token for Ticket A cannot reach Ticket B 3. CUSTOMER REPLY Verify: - the customer can reply to their own valid ticket - the customer cannot submit a reply as staff - the customer cannot send a reply to another ticket 4. STAFF Verify: - an authorized staff account can sign in - the ticket appears in the Support Dashboard - staff can reply - staff can change the status - staff can resolve the ticket 5. UNAUTHORIZED ACCESS Verify a public or non-staff account cannot list tickets, read private customer data, send staff replies, change a ticket status or reach Support Dashboard data. 6. CONVERSATION Run the whole thread: customer submits a ticket → staff replies → the customer sees the reply → the customer replies → staff see the customer reply → staff resolve it. Verify every message persists after a refresh. 7. SECRETS Confirm no Supabase Secret or service-role credential exists in browser code. Return exactly one: SUPPORT SYSTEM VERIFIED or: NEEDS ATTENTION Use SUPPORT SYSTEM VERIFIED only if public ticket creation works, private ticket access works, two-way replies work, staff authorization is enforced, status and resolution work, and cross-ticket access is blocked. If NEEDS ATTENTION, list only the remaining support-system issues. Do not perform a broad security or performance audit.
Complete one final test conversation from ticket submission to staff reply to customer reply to Resolved before deployment.
Deploy to Hostinger
Deploy the Support App
Before pushing to GitHub, check:
The project builds successfully
No .env or other secret files are being published
No Supabase Secret or service-role key is in browser code
You know the production environment variable names
You know which settings depend on the production URL
Push the project to GitHub with Claude Code or your normal GitHub workflow. If it already has a repository, use it.
Deploy on Hostinger
Recommended plan: Business
USED IN TUTORIAL
https://www.hostg.xyz/SHK1P
Hostinger Websites Add Website Node.js Web App
Choose Import Git repository Connect GitHub and select the support app's repository Review the detected framework and build settings Add the environment variables with your project's exact names Click Deploy and wait for the result
IMPORTANT
Add the Supabase Project URL and Publishable key under the names your project already uses. Private server keys stay private — never rename one to a VITE_ or NEXT_PUBLIC_ variable to make the deployment work.
Fix a Failed Website or Web App Deployment with AI
Use this if the Hostinger deployment fails.
/video/fix-a-failed-website-or-web-app-deployment-with-ai
Test the Live Support System
First add your live Hostinger URL in Supabase so staff sign-in works in production:
Supabase Authentication URL Configuration
Set the Site URL to your live URL and add it to the Redirect URLs. Then ask Claude Code to update every other production setting that depends on the live address — including the base URL used to build private ticket links, so a live ticket never points at localhost. Then open the live app and check:
Customer: the live Support page loads
Customer: submit a test ticket and save the private link
Customer: the private link uses your live domain
Staff: sign in and open the ticket
Staff: send a reply and set Waiting for Customer
Customer: refresh the private ticket and read the reply
Customer: send another reply
Staff: see the customer reply and mark it Resolved
Security: a random ticket token exposes nothing
Security: a signed-out visitor cannot reach the Support Dashboard
Mobile: the support form and the private ticket page work on a phone
Mobile: the dashboard stays usable on tablet and phone
Verify the Live Support System
Check the complete live customer-to-support workflow on Hostinger before using it with real customers.
Perform a focused production verification of this customer support ticket system, now live on Hostinger. Do not add features. Never print secret values. Do not claim to inspect Hostinger or Supabase dashboard state you cannot access — ask me for the live URL and to confirm specific settings when needed. 1. GITHUB - the approved source is current - no real secret files are committed 2. HOSTINGER - the deployment succeeded - the intended repository and branch are connected - the build and runtime configuration is correct - the required environment variables are configured 3. SUPABASE - the production app uses the intended Supabase project - staff authentication works - staff authorization is enforced - private server credentials remain private 4. CUSTOMER - ticket submission works - the private link uses the production domain, never localhost - a valid token opens the right ticket - an invalid token reveals nothing - a customer reply works 5. STAFF - the new ticket appears - a staff reply works - a status update works - resolution works 6. ACCESS - public users cannot list tickets - public users cannot read unrelated tickets - public users cannot impersonate staff - an unauthorized account cannot reach support operations Return exactly one: SUPPORT SYSTEM LIVE or: NEEDS ATTENTION If SUPPORT SYSTEM LIVE, summarize: ticket creation, private access, the staff dashboard, two-way replies, status and resolution, and the deployment result. If NEEDS ATTENTION, list only launch blockers.
Run one final live ticket from customer submission through a complete support conversation and mark it Resolved before using the system with real customers.