Deploying Frappe Helpdesk
Introduction
Frappe Helpdesk is a modern, open-source customer support platform built on the powerful Frappe Framework. Designed to streamline customer service operations, Frappe Helpdesk provides an intuitive interface for managing support tickets, tracking customer interactions, automating workflows, and delivering exceptional customer experiences. Built by the creators of ERPNext, Frappe Helpdesk inherits the robustness and flexibility of the Frappe Framework while focusing specifically on customer support use cases.
Frappe Helpdesk excels at:
- Ticket Management: Create, assign, prioritize, and track support tickets throughout their lifecycle
- Multi-Channel Support: Handle tickets from email, web forms, API integrations, and customer portals
- SLA Management: Set and monitor Service Level Agreements with automatic escalations
- Knowledge Base: Build a self-service help center with articles, FAQs, and documentation
- Automation: Create custom workflows, auto-assignments, and response templates
- Team Collaboration: Internal notes, ticket assignments, and team performance tracking
- Customer Portal: Allow customers to view ticket status and submit new requests
- Analytics & Reporting: Track response times, resolution rates, and agent performance
- Customization: Extend functionality with custom fields, doctypes, and integrations
- Email Integration: Send and receive emails directly from the helpdesk interface
- API-First Design: Integrate with existing tools through comprehensive REST API
- Mobile Responsive: Manage tickets on any device with adaptive UI
Common use cases include software support desks, IT helpdesk operations, customer service centers, technical support teams, SaaS product support, internal IT ticketing systems, and any business requiring structured customer communication management.
This comprehensive guide walks you through deploying Frappe Helpdesk on Klutch.sh using Docker, including MariaDB database setup, Redis configuration for caching and background jobs, persistent storage for data and files, environment variable management, and production-ready best practices.
Why Deploy Frappe Helpdesk on Klutch.sh?
- Simplified Deployment: Launch your helpdesk without managing complex server infrastructure
- Persistent Storage: Reliable volume storage for database, uploads, and backups
- Custom Domains: Use your own branded domain for professional customer interactions
- Environment Variables: Secure configuration management through the dashboard
- Automatic HTTPS: Built-in SSL certificates for secure customer data
- Easy Scaling: Adjust resources as your support volume grows
- Zero Downtime Updates: Deploy changes without interrupting customer service
- Container Benefits: Isolated, reproducible environment with version control
- GitHub Integration: Automated deployments from your repository
- Multi-Service Support: Deploy MariaDB, Redis, and Frappe Helpdesk independently
Prerequisites
Before you begin, ensure you have:
- A Klutch.sh account
- A GitHub account with a repository for your deployment
- Basic understanding of Docker and customer support operations
- (Recommended) MariaDB or MySQL database deployed on Klutch.sh - see our MariaDB deployment guide
- (Recommended) Redis deployed on Klutch.sh - see our Redis deployment guide
- Understanding of the Frappe Framework architecture
Understanding Frappe Helpdesk Architecture
Frappe Helpdesk is built on the Frappe Framework, which uses a multi-tier architecture:
Core Components:
- Python Backend (3.10+): Application logic, API endpoints, background jobs
- MariaDB/MySQL: Primary database for tickets, users, and configuration
- Redis: Caching layer and background job queue (Celery-like)
- Gunicorn: WSGI server for handling HTTP requests
- Node.js: Frontend asset compilation and build tools
- Frappe Framework: Meta-framework providing ORM, API, and admin interface
Key Features:
- Ticket lifecycle management with custom states
- Email-to-ticket conversion
- Automated SLA tracking and escalations
- Customer portal for self-service
- Internal knowledge base management
- Agent performance analytics
- Custom field types and workflows
- REST API for integrations
When deploying on Klutch.sh, we’ll containerize the Frappe Helpdesk application and connect it to separately deployed MariaDB and Redis instances for optimal performance and scalability.
Preparing Your Repository
Step 1: Create Project Directory
Create a new directory for your Frappe Helpdesk deployment:
mkdir frappe-helpdesk-klutchcd frappe-helpdesk-klutchStep 2: Create the Dockerfile
Create a Dockerfile in your project root. Klutch.sh will automatically detect and use this:
FROM frappe/bench:latest
# Set working directoryWORKDIR /home/frappe/frappe-bench
# Install system dependenciesUSER rootRUN apt-get update && apt-get install -y \ git \ wget \ curl \ vim \ mariadb-client \ redis-tools \ supervisor \ && rm -rf /var/lib/apt/lists/*
# Switch to frappe userUSER frappe
# Initialize bench if not existsRUN if [ ! -d "sites" ]; then \ bench init --skip-redis-config-generation frappe-bench || true; \ fi
WORKDIR /home/frappe/frappe-bench
# Get Frappe Helpdesk appRUN bench get-app --branch main https://github.com/frappe/helpdesk.git || \ (cd apps && git clone --branch main https://github.com/frappe/helpdesk.git)
# Create sites directory structureRUN mkdir -p sites/assets
# Copy configuration filesCOPY --chown=frappe:frappe common_site_config.json sites/common_site_config.json 2>/dev/null || trueCOPY --chown=frappe:frappe entrypoint.sh /usr/local/bin/RUN chmod +x /usr/local/bin/entrypoint.sh
# Expose portsEXPOSE 8000EXPOSE 9000
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \ CMD curl -f http://localhost:8000/api/method/ping || exit 1
# Set entrypointENTRYPOINT ["/usr/local/bin/entrypoint.sh"]CMD ["start"]Step 3: Create Configuration File
Create common_site_config.json for site-wide configuration:
{ "background_workers": 1, "db_host": "mariadb-db.klutch.sh", "db_port": 8000, "redis_cache": "redis://redis-cache.klutch.sh:8000", "redis_queue": "redis://redis-queue.klutch.sh:8000", "redis_socketio": "redis://redis-socketio.klutch.sh:8000", "socketio_port": 9000, "file_watcher_port": 6787, "webserver_port": 8000, "serve_default_site": true, "rebase_on_pull": false, "update_bench_on_update": false, "shallow_clone": true, "live_reload": false}Step 4: Create Entrypoint Script
Create entrypoint.sh to handle initialization and startup:
#!/bin/bashset -e
# Function to wait for servicewait_for_service() { local host=$1 local port=$2 local service=$3 echo "Waiting for $service to be ready..." for i in {1..30}; do if nc -z "$host" "$port" 2>/dev/null; then echo "$service is ready!" return 0 fi echo "Waiting for $service... attempt $i" sleep 2 done echo "Warning: $service is not responding" return 1}
# Wait for MariaDBif [ ! -z "$DB_HOST" ]; then wait_for_service "$DB_HOST" "${DB_PORT:-3306}" "MariaDB"fi
# Wait for Redisif [ ! -z "$REDIS_CACHE" ]; then REDIS_HOST=$(echo $REDIS_CACHE | sed -n 's/.*\/\/\([^:]*\).*/\1/p') REDIS_PORT=$(echo $REDIS_CACHE | sed -n 's/.*:\([0-9]*\).*/\1/p') wait_for_service "$REDIS_HOST" "${REDIS_PORT:-6379}" "Redis"fi
# Update common_site_config.json with environment variablescd /home/frappe/frappe-bench
if [ -f "sites/common_site_config.json" ]; then python3 << EOFimport jsonimport os
config_file = "sites/common_site_config.json"with open(config_file, "r") as f: config = json.load(f)
# Update from environment variablesif os.getenv("DB_HOST"): config["db_host"] = os.getenv("DB_HOST")if os.getenv("DB_PORT"): config["db_port"] = int(os.getenv("DB_PORT", "3306"))if os.getenv("REDIS_CACHE"): config["redis_cache"] = os.getenv("REDIS_CACHE")if os.getenv("REDIS_QUEUE"): config["redis_queue"] = os.getenv("REDIS_QUEUE")if os.getenv("REDIS_SOCKETIO"): config["redis_socketio"] = os.getenv("REDIS_SOCKETIO")
with open(config_file, "w") as f: json.dump(config, f, indent=2)EOFfi
# Check if site existsSITE_NAME="${SITE_NAME:-helpdesk.localhost}"
if [ ! -d "sites/$SITE_NAME" ]; then echo "Creating new site: $SITE_NAME" bench new-site "$SITE_NAME" \ --mariadb-root-password "${DB_ROOT_PASSWORD:-admin}" \ --admin-password "${ADMIN_PASSWORD:-admin}" \ --no-mariadb-socket || echo "Site creation failed, may already exist"
# Install Helpdesk app if [ -d "apps/helpdesk" ]; then echo "Installing Helpdesk app..." bench --site "$SITE_NAME" install-app helpdesk || echo "App may already be installed" fifi
# Set site as defaultif [ -f "sites/currentsite.txt" ]; then echo "$SITE_NAME" > sites/currentsite.txtfi
# Migrate databaseecho "Running migrations..."bench --site "$SITE_NAME" migrate || echo "Migration completed with warnings"
# Build assets if in productionif [ "$ENVIRONMENT" = "production" ]; then echo "Building assets for production..." bench build --app helpdesk || echo "Asset build completed"fi
# Clear cachebench --site "$SITE_NAME" clear-cache || true
# Start services based on commandcase "$1" in start) echo "Starting Frappe Helpdesk..." bench start ;; web) echo "Starting web server only..." bench serve --port 8000 --noreload ;; worker) echo "Starting background worker..." bench worker --queue default,short,long ;; schedule) echo "Starting scheduler..." bench schedule ;; *) exec "$@" ;;esacStep 5: Create .gitignore
Create a .gitignore file to exclude sensitive and generated files:
# Frappe Benchsites/*/private/sites/*/logs/sites/*/site_config.jsonsites/*/locks/sites/assets/*.pyc__pycache__/*.log
# Environment files.env.env.local.env.production*.pem*.key
# Database*.sql*.db
# Node modulesnode_modules/package-lock.json
# Logslogs/*.log
# OS files.DS_StoreThumbs.dbStep 6: Create Environment Configuration
Create a .env.example file for environment variable reference:
# Site ConfigurationSITE_NAME=helpdesk.example.comADMIN_PASSWORD=your_secure_admin_password
# Database Configuration (MariaDB)DB_HOST=mariadb-db.klutch.shDB_PORT=8000DB_ROOT_PASSWORD=your_secure_db_password
# Redis ConfigurationREDIS_CACHE=redis://redis-cache.klutch.sh:8000REDIS_QUEUE=redis://redis-queue.klutch.sh:8000REDIS_SOCKETIO=redis://redis-socketio.klutch.sh:8000
# Application ConfigurationENVIRONMENT=productionDEVELOPER_MODE=0
# Email Configuration (Optional)MAIL_SERVER=smtp.example.comMAIL_PORT=587MAIL_USE_TLS=1MAIL_LOGIN=noreply@example.comMAIL_PASSWORD=your_email_passwordStep 7: Create README
Create a README.md file with deployment instructions:
# Frappe Helpdesk on Klutch.sh
This repository contains the Docker configuration for deploying Frappe Helpdesk on Klutch.sh.
## Features
- Modern ticket management system- Multi-channel support (email, web, API)- SLA tracking and escalations- Customer portal- Knowledge base- Team collaboration tools- Analytics and reporting
## Prerequisites
1. MariaDB database deployed on Klutch.sh2. Redis instance deployed on Klutch.sh3. GitHub repository connected to Klutch.sh
## Deployment
1. Deploy MariaDB and Redis first (see Klutch.sh guides)2. Push this repository to GitHub3. Create a new app on Klutch.sh4. Connect your GitHub repository5. Configure environment variables6. Add persistent volumes: - `/home/frappe/frappe-bench/sites` (10GB+) - `/home/frappe/frappe-bench/logs` (5GB)7. Deploy!
## Environment Variables
See `.env.example` for all available configuration options.
## Access
After deployment, access Frappe Helpdesk at: https://your-app.klutch.sh
Default credentials (change immediately):- Username: Administrator- Password: (set via ADMIN_PASSWORD)
## Documentation
- <a href="https://docs.frappe.io/helpdesk" target="_blank" rel="noopener noreferrer">Frappe Helpdesk Docs</a>- <a href="https://frappeframework.com/docs" target="_blank" rel="noopener noreferrer">Frappe Framework Docs</a>Step 8: Initialize Git Repository
git initgit add .git commit -m "Initial Frappe Helpdesk deployment configuration"Push to GitHub:
git remote add origin https://github.com/yourusername/frappe-helpdesk-klutch.gitgit branch -M maingit push -u origin mainDeploying on Klutch.sh
Part 1: Deploy Supporting Services
-
Deploy MariaDB Database
Follow our MariaDB deployment guide to set up your database. Make note of:- Database host (e.g.,
mariadb-db.klutch.sh) - Database port (typically
8000) - Root password
- Database host (e.g.,
-
Deploy Redis Cache
Follow our Redis deployment guide to set up Redis for caching and background jobs. You may want to deploy 2-3 Redis instances for different purposes:- redis-cache: For application caching
- redis-queue: For background job queue
- redis-socketio: For real-time updates (optional)
-
Create Databases in MariaDB
Connect to your MariaDB instance and create the necessary database:CREATE DATABASE IF NOT EXISTS frappe_helpdeskCHARACTER SET utf8mb4COLLATE utf8mb4_unicode_ci;
Part 2: Deploy Frappe Helpdesk Application
-
Log in to Klutch.sh
Navigate to klutch.sh/app and sign in to your account. -
Create a New Project
Click New Project, give it a name (e.g., "Frappe Helpdesk"), and create the project. -
Create a New App
Inside your project, click New App and select your GitHub repository containing the Frappe Helpdesk configuration. -
Configure Traffic Type
In the app settings, select HTTP traffic since Frappe Helpdesk runs a web server. -
Set Environment Variables
Add the following environment variables in the Klutch.sh dashboard:Terminal window # Site Configuration (REQUIRED)SITE_NAME=helpdesk.example.comADMIN_PASSWORD=your_very_secure_admin_password# Database Configuration (REQUIRED)DB_HOST=mariadb-db.klutch.shDB_PORT=8000DB_ROOT_PASSWORD=your_mariadb_root_password# Redis Configuration (REQUIRED)REDIS_CACHE=redis://redis-cache.klutch.sh:8000REDIS_QUEUE=redis://redis-queue.klutch.sh:8000REDIS_SOCKETIO=redis://redis-socketio.klutch.sh:8000# Application ConfigurationENVIRONMENT=productionDEVELOPER_MODE=0# Email Configuration (Optional but recommended)MAIL_SERVER=smtp.example.comMAIL_PORT=587MAIL_USE_TLS=1MAIL_LOGIN=noreply@example.comMAIL_PASSWORD=your_email_passwordSecurity Note: Never commit credentials to your repository. Always set them as environment variables in the Klutch.sh dashboard.
-
Configure Persistent Volumes
Attach persistent volumes to store site data and logs:<strong>Volume 1 - Site Data:</strong><ul><li>Mount path: <code>/home/frappe/frappe-bench/sites</code></li><li>Size: <code>10 GB</code> (or more based on expected ticket volume)</li><li>Purpose: Stores site files, uploads, and database backups</li></ul><strong>Volume 2 - Logs:</strong><ul><li>Mount path: <code>/home/frappe/frappe-bench/logs</code></li><li>Size: <code>5 GB</code></li><li>Purpose: Application logs and error tracking</li></ul> -
Deploy the App
Click Deploy. Klutch.sh will automatically detect the Dockerfile and build your Frappe Helpdesk container. The first deployment may take 5-10 minutes as it builds assets and initializes the database. -
Access Frappe Helpdesk
Once deployed, access your helpdesk athttps://your-app-name.klutch.sh. Log in with:- Username:
Administrator - Password: The value you set for
ADMIN_PASSWORD
<strong>Important:</strong> Change the Administrator password immediately after first login for security. - Username:
-
Configure Custom Domain (Optional)
Set up a custom domain for professional branding. Update theSITE_NAMEenvironment variable to match your domain, then configure DNS to point to your Klutch.sh app.
Configuration
Site Settings
After logging in, configure your helpdesk:
- System Settings → Configure timezone, default language, and date formats
- Email Domain → Set up email receiving for ticket creation
- SLA Configuration → Define response and resolution time targets
- Team Setup → Add agents and assign roles
- Knowledge Base → Create help articles for self-service
Email Integration
Configure email settings for ticket creation and notifications:
- Navigate to Settings → Email Domain
- Add your support email domain
- Configure SMTP settings using environment variables
- Test email sending and receiving
- Set up auto-responders and email templates
Ticket Workflow
Customize ticket states and workflows:
- Ticket Types → Define categories (Technical, Billing, General, etc.)
- Priorities → Set up priority levels (Low, Medium, High, Critical)
- Status Workflow → Configure ticket states (Open, In Progress, Resolved, Closed)
- Auto-Assignment → Create rules for automatic ticket routing
- Escalation Rules → Set up SLA breach notifications
Customer Portal
Enable self-service for customers:
- Portal Settings → Activate customer portal
- Customize Branding → Add logo and brand colors
- Help Articles → Publish knowledge base content
- Portal Users → Invite customers or allow registration
- Portal Permissions → Configure what customers can see and do
API Configuration
Frappe Helpdesk provides a comprehensive REST API:
Generate API Keys:
# Access site shellbench --site helpdesk.example.com console
# Generate API key for userfrappe.generate_hash(length=15)API Endpoints:
GET /api/resource/HD Ticket- List ticketsPOST /api/resource/HD Ticket- Create ticketGET /api/resource/HD Ticket/{name}- Get ticket detailsPUT /api/resource/HD Ticket/{name}- Update ticketDELETE /api/resource/HD Ticket/{name}- Delete ticket
Performance Tuning
Optimize Frappe Helpdesk for better performance:
{ "background_workers": 2, "gunicorn_workers": 4, "socketio_port": 9000, "auto_email_id": "support@example.com", "always_use_account_email_id_as_sender": 1, "disable_website_cache": 0}Sample Usage
Creating Tickets via API
Use the REST API to create tickets programmatically:
Python Example:
import requestsimport json
class FrappeHelpdeskAPI: def __init__(self, base_url, api_key, api_secret): self.base_url = base_url.rstrip('/') self.headers = { 'Authorization': f'token {api_key}:{api_secret}', 'Content-Type': 'application/json' }
def create_ticket(self, subject, description, customer_email, priority='Medium'): """Create a new support ticket""" endpoint = f'{self.base_url}/api/resource/HD Ticket'
data = { 'subject': subject, 'description': description, 'raised_by': customer_email, 'priority': priority, 'status': 'Open' }
response = requests.post(endpoint, headers=self.headers, json=data) response.raise_for_status() return response.json()
def get_ticket(self, ticket_id): """Retrieve ticket details""" endpoint = f'{self.base_url}/api/resource/HD Ticket/{ticket_id}' response = requests.get(endpoint, headers=self.headers) response.raise_for_status() return response.json()
def update_ticket(self, ticket_id, **kwargs): """Update ticket fields""" endpoint = f'{self.base_url}/api/resource/HD Ticket/{ticket_id}' response = requests.put(endpoint, headers=self.headers, json=kwargs) response.raise_for_status() return response.json()
def add_comment(self, ticket_id, comment_text): """Add a comment to a ticket""" endpoint = f'{self.base_url}/api/resource/HD Ticket Comment'
data = { 'reference_ticket': ticket_id, 'comment': comment_text }
response = requests.post(endpoint, headers=self.headers, json=data) response.raise_for_status() return response.json()
def list_tickets(self, filters=None, fields=None, limit=20): """List tickets with optional filters""" endpoint = f'{self.base_url}/api/resource/HD Ticket'
params = { 'limit_page_length': limit }
if filters: params['filters'] = json.dumps(filters) if fields: params['fields'] = json.dumps(fields)
response = requests.get(endpoint, headers=self.headers, params=params) response.raise_for_status() return response.json()
# Usageapi = FrappeHelpdeskAPI( base_url='https://example-app.klutch.sh', api_key='your_api_key', api_secret='your_api_secret')
# Create a ticketticket = api.create_ticket( subject='Login Issues', description='Unable to access my account after password reset', customer_email='customer@example.com', priority='High')
print(f"Created ticket: {ticket['data']['name']}")
# Add commentapi.add_comment( ticket_id=ticket['data']['name'], comment_text='We are investigating this issue and will update you shortly.')
# List open ticketsopen_tickets = api.list_tickets( filters=[['status', '=', 'Open']], fields=['name', 'subject', 'priority', 'creation'])
print(f"Found {len(open_tickets['data'])} open tickets")Node.js Integration
const axios = require('axios');
class FrappeHelpdeskClient { constructor(baseUrl, apiKey, apiSecret) { this.baseUrl = baseUrl.replace(/\/$/, ''); this.auth = Buffer.from(`${apiKey}:${apiSecret}`).toString('base64'); this.client = axios.create({ baseURL: this.baseUrl, headers: { 'Authorization': `Basic ${this.auth}`, 'Content-Type': 'application/json' } }); }
async createTicket(data) { try { const response = await this.client.post('/api/resource/HD Ticket', data); return response.data; } catch (error) { console.error('Error creating ticket:', error.response?.data || error.message); throw error; } }
async getTicket(ticketId) { const response = await this.client.get(`/api/resource/HD Ticket/${ticketId}`); return response.data; }
async updateTicket(ticketId, updates) { const response = await this.client.put( `/api/resource/HD Ticket/${ticketId}`, updates ); return response.data; }
async listTickets(filters = {}, limit = 20) { const params = new URLSearchParams({ limit_page_length: limit });
if (Object.keys(filters).length > 0) { params.append('filters', JSON.stringify(filters)); }
const response = await this.client.get(`/api/resource/HD Ticket?${params}`); return response.data; }
async assignTicket(ticketId, agentEmail) { return this.updateTicket(ticketId, { _assign: JSON.stringify([agentEmail]) }); }
async closeTicket(ticketId, resolution) { return this.updateTicket(ticketId, { status: 'Closed', resolution: resolution }); }}
// Usageconst helpdesk = new FrappeHelpdeskClient( 'https://example-app.klutch.sh', 'your_api_key', 'your_api_secret');
// Create tickethelpdesk.createTicket({ subject: 'Feature Request: Dark Mode', description: 'It would be great to have a dark mode option', raised_by: 'user@example.com', priority: 'Low', ticket_type: 'Feature Request'}).then(ticket => { console.log('Ticket created:', ticket.data.name);
// Assign to agent return helpdesk.assignTicket(ticket.data.name, 'agent@example.com');}).then(() => { console.log('Ticket assigned successfully');}).catch(error => { console.error('Error:', error);});Go Client Library
package main
import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time")
type HelpdeskClient struct { BaseURL string APIKey string APISecret string Client *http.Client}
type Ticket struct { Name string `json:"name,omitempty"` Subject string `json:"subject"` Description string `json:"description"` RaisedBy string `json:"raised_by"` Priority string `json:"priority"` Status string `json:"status"` TicketType string `json:"ticket_type,omitempty"` Creation time.Time `json:"creation,omitempty"`}
type TicketResponse struct { Data Ticket `json:"data"`}
type TicketListResponse struct { Data []Ticket `json:"data"`}
func NewHelpdeskClient(baseURL, apiKey, apiSecret string) *HelpdeskClient { return &HelpdeskClient{ BaseURL: baseURL, APIKey: apiKey, APISecret: apiSecret, Client: &http.Client{ Timeout: 30 * time.Second, }, }}
func (c *HelpdeskClient) doRequest(method, endpoint string, body interface{}) (*http.Response, error) { url := fmt.Sprintf("%s%s", c.BaseURL, endpoint)
var reqBody io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return nil, err } reqBody = bytes.NewBuffer(jsonData) }
req, err := http.NewRequest(method, url, reqBody) if err != nil { return nil, err }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("token %s:%s", c.APIKey, c.APISecret))
return c.Client.Do(req)}
func (c *HelpdeskClient) CreateTicket(ticket Ticket) (*Ticket, error) { resp, err := c.doRequest("POST", "/api/resource/HD Ticket", ticket) if err != nil { return nil, err } defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("API error: %s", string(body)) }
var result TicketResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return &result.Data, nil}
func (c *HelpdeskClient) GetTicket(ticketID string) (*Ticket, error) { endpoint := fmt.Sprintf("/api/resource/HD Ticket/%s", ticketID) resp, err := c.doRequest("GET", endpoint, nil) if err != nil { return nil, err } defer resp.Body.Close()
var result TicketResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return &result.Data, nil}
func (c *HelpdeskClient) ListTickets(status string, limit int) ([]Ticket, error) { endpoint := fmt.Sprintf("/api/resource/HD Ticket?limit_page_length=%d", limit) if status != "" { endpoint += fmt.Sprintf(`&filters=[["status","=","%s"]]`, status) }
resp, err := c.doRequest("GET", endpoint, nil) if err != nil { return nil, err } defer resp.Body.Close()
var result TicketListResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err }
return result.Data, nil}
func (c *HelpdeskClient) UpdateTicketStatus(ticketID, status string) error { endpoint := fmt.Sprintf("/api/resource/HD Ticket/%s", ticketID) update := map[string]string{"status": status}
resp, err := c.doRequest("PUT", endpoint, update) if err != nil { return err } defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("API error: %s", string(body)) }
return nil}
func main() { client := NewHelpdeskClient( "https://example-app.klutch.sh", "your_api_key", "your_api_secret", )
// Create a ticket ticket := Ticket{ Subject: "API Integration Test", Description: "Testing Go client library integration", RaisedBy: "test@example.com", Priority: "Medium", Status: "Open", TicketType: "Bug", }
created, err := client.CreateTicket(ticket) if err != nil { fmt.Printf("Error creating ticket: %v\n", err) return }
fmt.Printf("Created ticket: %s\n", created.Name)
// List open tickets tickets, err := client.ListTickets("Open", 10) if err != nil { fmt.Printf("Error listing tickets: %v\n", err) return }
fmt.Printf("Found %d open tickets\n", len(tickets)) for _, t := range tickets { fmt.Printf(" - %s: %s (Priority: %s)\n", t.Name, t.Subject, t.Priority) }}Ruby Integration
require 'net/http'require 'json'require 'uri'
class FrappeHelpdeskClient def initialize(base_url, api_key, api_secret) @base_url = base_url.chomp('/') @api_key = api_key @api_secret = api_secret end
def create_ticket(subject:, description:, raised_by:, priority: 'Medium') data = { subject: subject, description: description, raised_by: raised_by, priority: priority, status: 'Open' }
post('/api/resource/HD Ticket', data) end
def get_ticket(ticket_id) get("/api/resource/HD Ticket/#{ticket_id}") end
def update_ticket(ticket_id, updates) put("/api/resource/HD Ticket/#{ticket_id}", updates) end
def list_tickets(filters: nil, limit: 20) params = { limit_page_length: limit } params[:filters] = filters.to_json if filters
query_string = URI.encode_www_form(params) get("/api/resource/HD Ticket?#{query_string}") end
def add_comment(ticket_id, comment_text) data = { reference_ticket: ticket_id, comment: comment_text }
post('/api/resource/HD Ticket Comment', data) end
private
def request(method, path, body = nil) uri = URI("#{@base_url}#{path}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if uri.scheme == 'https'
request_class = case method when :get then Net::HTTP::Get when :post then Net::HTTP::Post when :put then Net::HTTP::Put when :delete then Net::HTTP::Delete end
request = request_class.new(uri) request['Authorization'] = "token #{@api_key}:#{@api_secret}" request['Content-Type'] = 'application/json' request.body = body.to_json if body
response = http.request(request) JSON.parse(response.body) if response.body rescue => e puts "Request failed: #{e.message}" raise end
def get(path) request(:get, path) end
def post(path, data) request(:post, path, data) end
def put(path, data) request(:put, path, data) endend
# Usageclient = FrappeHelpdeskClient.new( 'https://example-app.klutch.sh', 'your_api_key', 'your_api_secret')
# Create ticketticket = client.create_ticket( subject: 'Billing Question', description: 'I have a question about my recent invoice', raised_by: 'customer@example.com', priority: 'High')
puts "Created ticket: #{ticket['data']['name']}"
# Add commentclient.add_comment( ticket['data']['name'], 'Thank you for contacting us. A billing specialist will review your inquiry.')
# List ticketsopen_tickets = client.list_tickets( filters: [['status', '=', 'Open']], limit: 10)
puts "Open tickets: #{open_tickets['data'].length}"Production Best Practices
Security Hardening
-
Change Default Credentials
Terminal window # Change Administrator password immediatelybench --site helpdesk.example.com set-admin-password NEW_PASSWORD -
Enable Two-Factor Authentication
Navigate to User Settings → Security → Enable 2FA for all admin accounts -
Configure Allowed Hosts
{"host_name": "https://helpdesk.example.com","allow_cors": "*","cors_allowed_origins": ["https://example.com"],"disable_website_cache": false}
-
Set Up Rate Limiting
Configure rate limits to prevent abuse:# In site_config.json{"rate_limit": {"window": 86400,"limit": 5000}} -
Enable HTTPS Only
Klutch.sh provides automatic HTTPS. Ensurehost_nameuseshttps:// -
Regular Security Updates
Terminal window # Update Frappe Helpdeskbench update --pull --patch --build --requirements
Performance Optimization
-
Configure Gunicorn Workers
{"gunicorn_workers": 4,"worker_class": "gthread","threads_per_worker": 2,"timeout": 120}
-
Enable Redis Caching
{"redis_cache": "redis://redis-cache.klutch.sh:8000","cache_ttl": 3600}
-
Optimize Database Queries
-- Add indexes for common queriesALTER TABLE `tabHD Ticket` ADD INDEX idx_status (status);ALTER TABLE `tabHD Ticket` ADD INDEX idx_priority (priority);ALTER TABLE `tabHD Ticket` ADD INDEX idx_creation (creation);
-
Enable Asset Compression
Terminal window # Build minified assetsbench build --app helpdesk --production -
Configure Background Workers
{"background_workers": 2,"always_use_account_email_id_as_sender": 1,"auto_email_id": "support@example.com"}
Backup Strategy
-
Database Backups
Terminal window # Manual backupbench --site helpdesk.example.com backup --with-files# Automated daily backupsbench --site helpdesk.example.com backup --with-files --compress# Restore from backupbench --site helpdesk.example.com restore database_backup.sql.gz --with-private-files -
Schedule Automated Backups
Create a backup script:#!/bin/bashSITE_NAME="helpdesk.example.com"BACKUP_DIR="/home/frappe/frappe-bench/sites/$SITE_NAME/private/backups"DATE=$(date +%Y%m%d_%H%M%S)cd /home/frappe/frappe-bench# Create backupbench --site $SITE_NAME backup --with-files --compress# Upload to S3 (optional)aws s3 cp $BACKUP_DIR s3://your-bucket/helpdesk-backups/$DATE/ --recursive# Clean old backups (keep last 30 days)find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete -
Test Backup Restoration
Regularly test backup restoration to ensure data integrity
Monitoring and Logging
-
Application Logs
Terminal window # View real-time logstail -f /home/frappe/frappe-bench/logs/web.logtail -f /home/frappe/frappe-bench/logs/worker.logtail -f /home/frappe/frappe-bench/logs/schedule.log -
Monitor Ticket Metrics
# Get ticket statisticsfrappe.db.sql("""SELECTstatus,priority,COUNT(*) as count,AVG(TIMESTAMPDIFF(HOUR, creation, modified)) as avg_resolution_timeFROM `tabHD Ticket`WHERE creation > DATE_SUB(NOW(), INTERVAL 30 DAY)GROUP BY status, priority""")
-
Set Up Health Checks
Klutch.sh automatically monitors the health check endpoint defined in the Dockerfile -
Configure Error Notifications
{"error_report_email": "admin@example.com","error_report_frequency": "daily"}
Scaling Considerations
-
Vertical Scaling
Increase resources through Klutch.sh dashboard:- More CPU for handling concurrent requests
- More memory for caching and background jobs
- Larger storage for ticket attachments
-
Database Optimization
-- Optimize tables monthlyOPTIMIZE TABLE `tabHD Ticket`;OPTIMIZE TABLE `tabHD Ticket Comment`;-- Analyze query performanceEXPLAIN SELECT * FROM `tabHD Ticket` WHERE status = 'Open';
-
Separate Background Workers
Deploy dedicated containers for background jobs:# Worker-only containerCMD ["worker"] -
Load Testing
Terminal window # Test API endpointsab -n 1000 -c 50 https://example-app.klutch.sh/api/method/ping
Troubleshooting
Site Not Loading
Problem: Frappe Helpdesk site doesn’t load after deployment.
Solution:
- Check if site was created successfully:
Terminal window bench --site helpdesk.example.com console - Verify database connection:
Terminal window bench --site helpdesk.example.com mariadb - Check logs for errors:
Terminal window tail -f /home/frappe/frappe-bench/logs/web.log - Ensure environment variables are set correctly
- Verify persistent volumes are mounted properly
Database Connection Failed
Problem: Cannot connect to MariaDB database.
Solution:
- Verify database credentials in environment variables
- Check if MariaDB is running and accessible:
Terminal window mysql -h mariadb-db.klutch.sh -P 8000 -u root -p - Ensure database exists:
SHOW DATABASES;USE frappe_helpdesk;
- Check
common_site_config.jsonhas correct host and port - Review network connectivity from app to database
Redis Connection Issues
Problem: Background jobs not processing or caching not working.
Solution:
- Test Redis connectivity:
Terminal window redis-cli -h redis-cache.klutch.sh -p 8000 PING - Verify Redis URLs in environment variables
- Check Redis server is running and accepting connections
- Review
common_site_config.jsonRedis configuration - Restart background workers:
Terminal window bench --site helpdesk.example.com restart
Email Not Sending
Problem: Email notifications not being sent to customers.
Solution:
- Verify SMTP configuration in environment variables
- Test SMTP connection:
import smtplibserver = smtplib.SMTP('smtp.example.com', 587)server.starttls()server.login('user', 'password')server.quit()
- Check email queue:
Terminal window bench --site helpdesk.example.com show-pending-emails - Review email domain settings in Frappe Helpdesk
- Ensure firewall allows outbound SMTP traffic
Assets Not Loading
Problem: CSS/JS files not loading properly.
Solution:
- Rebuild assets:
Terminal window bench build --app helpdesk --force - Clear cache:
Terminal window bench --site helpdesk.example.com clear-cache - Check file permissions:
Terminal window ls -la /home/frappe/frappe-bench/sites/assets/ - Verify Node.js is installed and accessible
- Review build logs for errors
Slow Performance
Problem: Helpdesk responds slowly to requests.
Solution:
- Check database query performance:
SHOW PROCESSLIST;
- Optimize database tables:
OPTIMIZE TABLE `tabHD Ticket`;
- Increase Gunicorn workers in configuration
- Enable Redis caching if not already active
- Review and optimize custom scripts
- Scale up resources in Klutch.sh dashboard
Ticket Auto-Assignment Not Working
Problem: Tickets not being automatically assigned to agents.
Solution:
- Verify assignment rules are configured in Settings → Assignment Rules
- Check background workers are running:
Terminal window ps aux | grep worker - Review assignment rule conditions and priorities
- Ensure agents have proper roles and permissions
- Check logs for assignment errors
Additional Resources
- Frappe Helpdesk Documentation
- Frappe Framework Documentation
- Frappe Helpdesk GitHub Repository
- Frappe Helpdesk Official Website
- MariaDB Deployment Guide
- Redis Deployment Guide
- Klutch.sh Persistent Volumes
- Klutch.sh Deployments
You now have a fully functional Frappe Helpdesk deployment on Klutch.sh! This modern customer support platform provides comprehensive ticket management, SLA tracking, knowledge base capabilities, and team collaboration tools—all running on a scalable, secure infrastructure with complete control over your customer data and support operations.