Skip to main content
This tutorial walks through building an AI-powered support ticket classifier using Node.js, LangChain, and OpenAI. The system takes unstructured support tickets and extracts structured metadata: category, urgency, sentiment, customer info, and suggested actions. Form processing is one of the more practical AI applications. Instead of manually reading tickets, routing them, and extracting data, you let the model do it.

What you’re building

A web app with a form where users paste support tickets. The AI analyzes each ticket and returns structured JSON:
  • Category (Billing, Technical, Feature Request, Bug Report, Account, or General Inquiry)
  • Urgency (Critical, High, Medium, or Low)
  • Sentiment (Positive, Neutral, or Negative)
  • Product or service mentioned, if any
  • Customer name and email, if provided
  • Reference numbers like ticket IDs, project IDs, invoice numbers
  • A one-sentence summary
  • Suggested actions for the support team
The frontend displays results as formatted HTML with color-coded badges, plus a toggle to see the raw JSON.

Prerequisites

You’ll need Node.js 22+, npm, an OpenAI API key from platform.openai.com, the Upsun CLI, and Git.

Project setup

Create the project:
Install dependencies:
That’s express for the web server, dotenv for env files, cors for cross-origin requests, LangChain packages for working with OpenAI, TypeScript for type safety, tsx to run TypeScript directly, and Biome for linting. Configure TypeScript (tsconfig.json): View source on GitHub
Update package.json:

Building the classifier

1. The classification logic

This is where the AI does its work. We send the ticket to OpenAI with a detailed system prompt that explains the classification schema. Create src/classifier.ts: View source on GitHub
Worth noting: Temperature is set to 0. For classification tasks, you want deterministic output. The same ticket should always get the same category. The prompt lists all valid values explicitly. Without that, you might get “Tech Support” instead of “Technical” or “Urgent” instead of “High.” We handle markdown code blocks. Sometimes the model wraps JSON in triple backticks. The regex strips those out. Validation happens after parsing. If the model returns malformed JSON or misses required fields, we throw an error rather than returning garbage.

2. Input validation

Support tickets can be any length, but we need sensible limits. Create src/validation.ts: View source on GitHub
Ten thousand characters is generous. Most support tickets run under 2,000. The minimum of 10 catches empty or near-empty submissions.

3. Rate limiting

Prevent abuse with a simple in-memory rate limiter. Create src/rate-limiter.ts: View source on GitHub
Twenty requests per minute per IP. The cleanup interval prevents memory from growing unbounded.

4. Express server

Create src/index.ts: View source on GitHub
One POST endpoint that accepts a ticket, classifies it, and returns JSON. We log timing for monitoring.

5. Frontend

Create public/index.html. The full file is in the repository. It has:
  • Header with title and description
  • Example tickets section with six pre-written Upsun-themed tickets
  • Form with textarea and submit button
  • Results section showing classification with color-coded badges
  • JSON toggle to view raw response
The example tickets cover billing (plan upgrade request), technical (deployment failure), feature request (autoscaling), bug report (CLI crash), account (team permissions), and general inquiry (platform comparison). Check the repo for the complete HTML/CSS/JS.

Local development

Create .env:
Create .env.example for documentation: View source on GitHub
Run the dev server:
Open http://localhost:3000. Click an example ticket, hit “Classify ticket,” and watch the results appear.

Deploying to Upsun

Create .upsun/config.yaml: View source on GitHub
Initialize Git:
Create Upsun project:
Follow the prompts for organization, name, region, and plan. Set the OpenAI API key:
Deploy:
Get the URL:

Testing

Try the example tickets. Each should classify correctly: Monitor logs:
You’ll see request logs with timing and categories.

Extending the classifier

Add more categories

Edit the system prompt in src/classifier.ts:
Update the ClassificationResult interface if needed.

Extract more fields

Add fields to the prompt and interface:

Connect to a ticketing system

Instead of just displaying results, send them somewhere:

Add batch processing

Process multiple tickets at once:

Store results

Add PostgreSQL for persistence:

Cost considerations

Each classification uses roughly 500-1,500 tokens depending on ticket length. With gpt-4o-mini: For high volume, consider caching identical tickets, using embeddings to find similar past tickets, batching requests, or fine-tuning a smaller model.

Troubleshooting

”OPENAI_API_KEY is required” error

Check if the variable exists:
If missing, create it (see deployment section).

Classification returns unexpected categories

The model might be using its own judgment. Make the prompt more explicit:

JSON parsing fails

Sometimes the model adds extra text. Make the prompt stricter:

High latency

Classification should take 1-3 seconds. If slower:
  1. Check if you’re hitting rate limits
  2. Try a different OpenAI region
  3. Consider caching common ticket patterns

Rate limiting too strict

Adjust in src/rate-limiter.ts:

What’s next

You’ve got a ticket classifier that extracts structured data from unstructured text and deploys to Upsun. The same pattern works for other form processing: job applications, feedback forms, bug reports, customer inquiries. Anywhere you have unstructured text that needs structure, this approach works.

Resources

For questions, check the Upsun Community Forum or open an issue in this repo.
Last modified on May 13, 2026