A lightweight ticketing and RMA workflow tool built by a tech support engineer, for tech support engineers.
π Live demo: supportops.vercel.app πΈ Screenshots: see below
- Email notification on ticket status changes
- Bulk ticket operations (assign, close, export)
- SLA timer per ticket with overdue alerts
- Customer-facing ticket portal
- Analytics dashboard with resolution time metrics
- Slack/Teams webhook integration
- Architecture
- Local development
- Deployment
- API surface
- Screenshots
- Roadmap
- Contributing
- License
β±οΈ First load can take up to a minute. The demo backend runs on Render's free tier, which sleeps after 15 min of inactivity. The frontend fires a warm-up ping on app load and shows an informative loading state while the server wakes β so just give it a moment on the first visit. Subsequent requests are <1s.
After 9 years on the front lines of technical support, I kept hitting the same walls with the tools I was given:
- Tickets, customers, and devices lived in three different systems that didn't talk to each other.
- RMA tracking was a spreadsheet someone forgot to update.
- Status changes had no audit trail when a customer asked "who closed my ticket and why?"
- The "enterprise" platforms were slow, bloated, and built for managers β not the agent actually working the queue.
SupportOps is the tool I wished I had. Tickets, devices, customers, RMAs, and a full status history β in one place, fast, and built around how support actually works.
If you're reviewing this as a portfolio piece, the interesting bits are:
- FastAPI + Pydantic as a thin validation/business-rules layer over Supabase's PostgREST API β no ORM, no schema duplication.
- Automatic audit logging β every ticket status change appends a row to
ticket_historyfrom the API layer, not the client. - AI Resolution Suggester β Anthropic Claude (Haiku) reads a ticket plus its full notes history and proposes next steps. Wired in
backend/ai.py. - Resilient frontend β
frontend/src/api.jswrapsfetchwithAbortControllertimeouts, status checks, and auseApiResourcehook that gives every page the same loading / error / retry UX. - Cold-start UX β the frontend warms the backend on mount and explains the wait, so a sleeping free-tier dyno never silently looks like a broken app.
- π« Tickets with status, priority, assignee, and linked customer + device
- π₯ Customers directory (name, email, phone, company)
- π» Devices tied to customers by serial number and product type
- π Ticket notes for agent-side context and handoffs
- π Automatic status history β every status change is logged with who and when
- π¦ RMA tracking β RMA number, serial, shipping status, resolution status, linked to the originating ticket
- π€ AI Suggestions β Claude-powered next-step recommendations per ticket
| Layer | Tech |
|---|---|
| Frontend | React 19, Vite, Tailwind CSS v4, React Router |
| Backend | FastAPI (Python 3.11+), Pydantic |
| Database | Supabase (Postgres + PostgREST) |
| AI | Anthropic Claude Haiku 4.5 |
| Hosting | Vercel (frontend) Β· Render (backend) |
βββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β React + Vite β ββββΆ β FastAPI β ββββΆ β Supabase β
β (Vercel) β β (Render) β β (PostgREST) β
βββββββββββββββββ ββββββββ¬ββββββββ ββββββββββββββββ
β
βΌ
ββββββββββββββββ
β Anthropic β
β Claude β
ββββββββββββββββ
The FastAPI layer is intentionally thin β it validates with Pydantic, applies business rules (e.g. writing to ticket_history on every status change), proxies CRUD to Supabase's REST API, and brokers calls to Claude for the AI Suggester.
docker compose up --buildDocker starts the API at http://localhost:8000. Create backend/.env from backend/.env.example before first run.
- Python 3.11+
- Node.js 20+
- A Supabase project (free tier is fine) β grab your
SUPABASE_URLandSUPABASE_KEY - (Optional) An Anthropic API key if you want the AI Suggester to work locally
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pre-commit install
cp .env.example .env # then fill in SUPABASE_URL, SUPABASE_KEY, ANTHROPIC_API_KEY
uvicorn main:app --reloadAPI runs at http://localhost:8000. Health check: GET /health. Interactive docs: /docs.
cd frontend
cp .env.example .env # set VITE_API_BASE_URL=http://localhost:8000
npm install
npm run devApp runs at http://localhost:5173.
- Frontend β Vercel, SPA rewrites in
frontend/vercel.json. Env:VITE_API_BASE_URL. - Backend β Render web service from
render.yamlat repo root. Python 3.11.9. Env:SUPABASE_URL,SUPABASE_KEY,ANTHROPIC_API_KEY,ANTHROPIC_MODEL. Health check:/health.
Free-tier dynos sleep after 15 min of inactivity. The frontend warms the backend on app load β see frontend/src/api.js (warmupBackend).
| Resource | Endpoints |
|---|---|
| Health | GET /health |
| Dashboard | GET /dashboard |
| Customers | GET/POST/PUT/DELETE /customers |
| Devices | GET/POST/PUT /devices |
| Tickets | GET/POST/PUT /tickets, GET /tickets/{id} |
| Ticket Notes | GET /tickets/{id}/notes, POST /notes |
| Ticket Hist. | GET /tickets/{id}/history (auto-appended) |
| RMAs | GET/POST/PUT /rmas |
| AI | POST /tickets/{id}/ai-suggestions |
Status changes on tickets automatically append a row to ticket_history.
Full OpenAPI spec available at http://localhost:8000/docs when the backend is running.
- Full-text search across tickets and notes
- SLA timers with breach alerts
- Saved views / filters per agent
- CSV export of tickets and RMAs
- Role-based permissions (agent / lead / admin)
Issues and PRs are welcome β especially from anyone who's worked a support queue and has opinions on what's missing. Please open an issue describing the change before sending a large PR.
MIT Β© Sonny May


