Skip to content

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:

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:

Terminal window
mkdir frappe-helpdesk-klutch
cd frappe-helpdesk-klutch

Step 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 directory
WORKDIR /home/frappe/frappe-bench
# Install system dependencies
USER root
RUN 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 user
USER frappe
# Initialize bench if not exists
RUN if [ ! -d "sites" ]; then \
bench init --skip-redis-config-generation frappe-bench || true; \
fi
WORKDIR /home/frappe/frappe-bench
# Get Frappe Helpdesk app
RUN 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 structure
RUN mkdir -p sites/assets
# Copy configuration files
COPY --chown=frappe:frappe common_site_config.json sites/common_site_config.json 2>/dev/null || true
COPY --chown=frappe:frappe entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
# Expose ports
EXPOSE 8000
EXPOSE 9000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
CMD curl -f http://localhost:8000/api/method/ping || exit 1
# Set entrypoint
ENTRYPOINT ["/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/bash
set -e
# Function to wait for service
wait_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 MariaDB
if [ ! -z "$DB_HOST" ]; then
wait_for_service "$DB_HOST" "${DB_PORT:-3306}" "MariaDB"
fi
# Wait for Redis
if [ ! -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 variables
cd /home/frappe/frappe-bench
if [ -f "sites/common_site_config.json" ]; then
python3 << EOF
import json
import os
config_file = "sites/common_site_config.json"
with open(config_file, "r") as f:
config = json.load(f)
# Update from environment variables
if 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)
EOF
fi
# Check if site exists
SITE_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"
fi
fi
# Set site as default
if [ -f "sites/currentsite.txt" ]; then
echo "$SITE_NAME" > sites/currentsite.txt
fi
# Migrate database
echo "Running migrations..."
bench --site "$SITE_NAME" migrate || echo "Migration completed with warnings"
# Build assets if in production
if [ "$ENVIRONMENT" = "production" ]; then
echo "Building assets for production..."
bench build --app helpdesk || echo "Asset build completed"
fi
# Clear cache
bench --site "$SITE_NAME" clear-cache || true
# Start services based on command
case "$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 "$@"
;;
esac

Step 5: Create .gitignore

Create a .gitignore file to exclude sensitive and generated files:

# Frappe Bench
sites/*/private/
sites/*/logs/
sites/*/site_config.json
sites/*/locks/
sites/assets/
*.pyc
__pycache__/
*.log
# Environment files
.env
.env.local
.env.production
*.pem
*.key
# Database
*.sql
*.db
# Node modules
node_modules/
package-lock.json
# Logs
logs/
*.log
# OS files
.DS_Store
Thumbs.db

Step 6: Create Environment Configuration

Create a .env.example file for environment variable reference:

Terminal window
# Site Configuration
SITE_NAME=helpdesk.example.com
ADMIN_PASSWORD=your_secure_admin_password
# Database Configuration (MariaDB)
DB_HOST=mariadb-db.klutch.sh
DB_PORT=8000
DB_ROOT_PASSWORD=your_secure_db_password
# Redis Configuration
REDIS_CACHE=redis://redis-cache.klutch.sh:8000
REDIS_QUEUE=redis://redis-queue.klutch.sh:8000
REDIS_SOCKETIO=redis://redis-socketio.klutch.sh:8000
# Application Configuration
ENVIRONMENT=production
DEVELOPER_MODE=0
# Email Configuration (Optional)
MAIL_SERVER=smtp.example.com
MAIL_PORT=587
MAIL_USE_TLS=1
MAIL_LOGIN=noreply@example.com
MAIL_PASSWORD=your_email_password

Step 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.sh
2. Redis instance deployed on Klutch.sh
3. GitHub repository connected to Klutch.sh
## Deployment
1. Deploy MariaDB and Redis first (see Klutch.sh guides)
2. Push this repository to GitHub
3. Create a new app on Klutch.sh
4. Connect your GitHub repository
5. Configure environment variables
6. 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

Terminal window
git init
git add .
git commit -m "Initial Frappe Helpdesk deployment configuration"

Push to GitHub:

Terminal window
git remote add origin https://github.com/yourusername/frappe-helpdesk-klutch.git
git branch -M main
git push -u origin main

Deploying on Klutch.sh

Part 1: Deploy Supporting Services

  1. 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
  2. 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)
  3. Create Databases in MariaDB
    Connect to your MariaDB instance and create the necessary database:
    CREATE DATABASE IF NOT EXISTS frappe_helpdesk
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Part 2: Deploy Frappe Helpdesk Application

  1. Log in to Klutch.sh
    Navigate to klutch.sh/app and sign in to your account.
  2. Create a New Project
    Click New Project, give it a name (e.g., "Frappe Helpdesk"), and create the project.
  3. Create a New App
    Inside your project, click New App and select your GitHub repository containing the Frappe Helpdesk configuration.
  4. Configure Traffic Type
    In the app settings, select HTTP traffic since Frappe Helpdesk runs a web server.
  5. Set Environment Variables
    Add the following environment variables in the Klutch.sh dashboard:
    Terminal window
    # Site Configuration (REQUIRED)
    SITE_NAME=helpdesk.example.com
    ADMIN_PASSWORD=your_very_secure_admin_password
    # Database Configuration (REQUIRED)
    DB_HOST=mariadb-db.klutch.sh
    DB_PORT=8000
    DB_ROOT_PASSWORD=your_mariadb_root_password
    # Redis Configuration (REQUIRED)
    REDIS_CACHE=redis://redis-cache.klutch.sh:8000
    REDIS_QUEUE=redis://redis-queue.klutch.sh:8000
    REDIS_SOCKETIO=redis://redis-socketio.klutch.sh:8000
    # Application Configuration
    ENVIRONMENT=production
    DEVELOPER_MODE=0
    # Email Configuration (Optional but recommended)
    MAIL_SERVER=smtp.example.com
    MAIL_PORT=587
    MAIL_USE_TLS=1
    MAIL_LOGIN=noreply@example.com
    MAIL_PASSWORD=your_email_password

    Security Note: Never commit credentials to your repository. Always set them as environment variables in the Klutch.sh dashboard.

  6. 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>
  7. 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.
  8. Access Frappe Helpdesk
    Once deployed, access your helpdesk at https://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.
  9. Configure Custom Domain (Optional)
    Set up a custom domain for professional branding. Update the SITE_NAME environment variable to match your domain, then configure DNS to point to your Klutch.sh app.

Configuration

Site Settings

After logging in, configure your helpdesk:

  1. System Settings → Configure timezone, default language, and date formats
  2. Email Domain → Set up email receiving for ticket creation
  3. SLA Configuration → Define response and resolution time targets
  4. Team Setup → Add agents and assign roles
  5. Knowledge Base → Create help articles for self-service

Email Integration

Configure email settings for ticket creation and notifications:

  1. Navigate to SettingsEmail Domain
  2. Add your support email domain
  3. Configure SMTP settings using environment variables
  4. Test email sending and receiving
  5. Set up auto-responders and email templates

Ticket Workflow

Customize ticket states and workflows:

  1. Ticket Types → Define categories (Technical, Billing, General, etc.)
  2. Priorities → Set up priority levels (Low, Medium, High, Critical)
  3. Status Workflow → Configure ticket states (Open, In Progress, Resolved, Closed)
  4. Auto-Assignment → Create rules for automatic ticket routing
  5. Escalation Rules → Set up SLA breach notifications

Customer Portal

Enable self-service for customers:

  1. Portal Settings → Activate customer portal
  2. Customize Branding → Add logo and brand colors
  3. Help Articles → Publish knowledge base content
  4. Portal Users → Invite customers or allow registration
  5. Portal Permissions → Configure what customers can see and do

API Configuration

Frappe Helpdesk provides a comprehensive REST API:

Generate API Keys:

Terminal window
# Access site shell
bench --site helpdesk.example.com console
# Generate API key for user
frappe.generate_hash(length=15)

API Endpoints:

  • GET /api/resource/HD Ticket - List tickets
  • POST /api/resource/HD Ticket - Create ticket
  • GET /api/resource/HD Ticket/{name} - Get ticket details
  • PUT /api/resource/HD Ticket/{name} - Update ticket
  • DELETE /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 requests
import 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()
# Usage
api = FrappeHelpdeskAPI(
base_url='https://example-app.klutch.sh',
api_key='your_api_key',
api_secret='your_api_secret'
)
# Create a ticket
ticket = 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 comment
api.add_comment(
ticket_id=ticket['data']['name'],
comment_text='We are investigating this issue and will update you shortly.'
)
# List open tickets
open_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
});
}
}
// Usage
const helpdesk = new FrappeHelpdeskClient(
'https://example-app.klutch.sh',
'your_api_key',
'your_api_secret'
);
// Create ticket
helpdesk.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)
end
end
# Usage
client = FrappeHelpdeskClient.new(
'https://example-app.klutch.sh',
'your_api_key',
'your_api_secret'
)
# Create ticket
ticket = 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 comment
client.add_comment(
ticket['data']['name'],
'Thank you for contacting us. A billing specialist will review your inquiry.'
)
# List tickets
open_tickets = client.list_tickets(
filters: [['status', '=', 'Open']],
limit: 10
)
puts "Open tickets: #{open_tickets['data'].length}"

Production Best Practices

Security Hardening

  1. Change Default Credentials
    Terminal window
    # Change Administrator password immediately
    bench --site helpdesk.example.com set-admin-password NEW_PASSWORD
  2. Enable Two-Factor Authentication
    Navigate to User SettingsSecurity → Enable 2FA for all admin accounts
  3. Configure Allowed Hosts
    {
    "host_name": "https://helpdesk.example.com",
    "allow_cors": "*",
    "cors_allowed_origins": ["https://example.com"],
    "disable_website_cache": false
    }
  4. Set Up Rate Limiting
    Configure rate limits to prevent abuse:
    # In site_config.json
    {
    "rate_limit": {
    "window": 86400,
    "limit": 5000
    }
    }
  5. Enable HTTPS Only
    Klutch.sh provides automatic HTTPS. Ensure host_name uses https://
  6. Regular Security Updates
    Terminal window
    # Update Frappe Helpdesk
    bench update --pull --patch --build --requirements

Performance Optimization

  1. Configure Gunicorn Workers
    {
    "gunicorn_workers": 4,
    "worker_class": "gthread",
    "threads_per_worker": 2,
    "timeout": 120
    }
  2. Enable Redis Caching
    {
    "redis_cache": "redis://redis-cache.klutch.sh:8000",
    "cache_ttl": 3600
    }
  3. Optimize Database Queries
    -- Add indexes for common queries
    ALTER 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);
  4. Enable Asset Compression
    Terminal window
    # Build minified assets
    bench build --app helpdesk --production
  5. Configure Background Workers
    {
    "background_workers": 2,
    "always_use_account_email_id_as_sender": 1,
    "auto_email_id": "support@example.com"
    }

Backup Strategy

  1. Database Backups
    Terminal window
    # Manual backup
    bench --site helpdesk.example.com backup --with-files
    # Automated daily backups
    bench --site helpdesk.example.com backup --with-files --compress
    # Restore from backup
    bench --site helpdesk.example.com restore database_backup.sql.gz --with-private-files
  2. Schedule Automated Backups
    Create a backup script:
    #!/bin/bash
    SITE_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 backup
    bench --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
  3. Test Backup Restoration
    Regularly test backup restoration to ensure data integrity

Monitoring and Logging

  1. Application Logs
    Terminal window
    # View real-time logs
    tail -f /home/frappe/frappe-bench/logs/web.log
    tail -f /home/frappe/frappe-bench/logs/worker.log
    tail -f /home/frappe/frappe-bench/logs/schedule.log
  2. Monitor Ticket Metrics
    # Get ticket statistics
    frappe.db.sql("""
    SELECT
    status,
    priority,
    COUNT(*) as count,
    AVG(TIMESTAMPDIFF(HOUR, creation, modified)) as avg_resolution_time
    FROM `tabHD Ticket`
    WHERE creation > DATE_SUB(NOW(), INTERVAL 30 DAY)
    GROUP BY status, priority
    """)
  3. Set Up Health Checks
    Klutch.sh automatically monitors the health check endpoint defined in the Dockerfile
  4. Configure Error Notifications
    {
    "error_report_email": "admin@example.com",
    "error_report_frequency": "daily"
    }

Scaling Considerations

  1. 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
  2. Database Optimization
    -- Optimize tables monthly
    OPTIMIZE TABLE `tabHD Ticket`;
    OPTIMIZE TABLE `tabHD Ticket Comment`;
    -- Analyze query performance
    EXPLAIN SELECT * FROM `tabHD Ticket` WHERE status = 'Open';
  3. Separate Background Workers
    Deploy dedicated containers for background jobs:
    # Worker-only container
    CMD ["worker"]
  4. Load Testing
    Terminal window
    # Test API endpoints
    ab -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:

  1. Check if site was created successfully:
    Terminal window
    bench --site helpdesk.example.com console
  2. Verify database connection:
    Terminal window
    bench --site helpdesk.example.com mariadb
  3. Check logs for errors:
    Terminal window
    tail -f /home/frappe/frappe-bench/logs/web.log
  4. Ensure environment variables are set correctly
  5. Verify persistent volumes are mounted properly

Database Connection Failed

Problem: Cannot connect to MariaDB database.

Solution:

  1. Verify database credentials in environment variables
  2. Check if MariaDB is running and accessible:
    Terminal window
    mysql -h mariadb-db.klutch.sh -P 8000 -u root -p
  3. Ensure database exists:
    SHOW DATABASES;
    USE frappe_helpdesk;
  4. Check common_site_config.json has correct host and port
  5. Review network connectivity from app to database

Redis Connection Issues

Problem: Background jobs not processing or caching not working.

Solution:

  1. Test Redis connectivity:
    Terminal window
    redis-cli -h redis-cache.klutch.sh -p 8000 PING
  2. Verify Redis URLs in environment variables
  3. Check Redis server is running and accepting connections
  4. Review common_site_config.json Redis configuration
  5. Restart background workers:
    Terminal window
    bench --site helpdesk.example.com restart

Email Not Sending

Problem: Email notifications not being sent to customers.

Solution:

  1. Verify SMTP configuration in environment variables
  2. Test SMTP connection:
    import smtplib
    server = smtplib.SMTP('smtp.example.com', 587)
    server.starttls()
    server.login('user', 'password')
    server.quit()
  3. Check email queue:
    Terminal window
    bench --site helpdesk.example.com show-pending-emails
  4. Review email domain settings in Frappe Helpdesk
  5. Ensure firewall allows outbound SMTP traffic

Assets Not Loading

Problem: CSS/JS files not loading properly.

Solution:

  1. Rebuild assets:
    Terminal window
    bench build --app helpdesk --force
  2. Clear cache:
    Terminal window
    bench --site helpdesk.example.com clear-cache
  3. Check file permissions:
    Terminal window
    ls -la /home/frappe/frappe-bench/sites/assets/
  4. Verify Node.js is installed and accessible
  5. Review build logs for errors

Slow Performance

Problem: Helpdesk responds slowly to requests.

Solution:

  1. Check database query performance:
    SHOW PROCESSLIST;
  2. Optimize database tables:
    OPTIMIZE TABLE `tabHD Ticket`;
  3. Increase Gunicorn workers in configuration
  4. Enable Redis caching if not already active
  5. Review and optimize custom scripts
  6. Scale up resources in Klutch.sh dashboard

Ticket Auto-Assignment Not Working

Problem: Tickets not being automatically assigned to agents.

Solution:

  1. Verify assignment rules are configured in SettingsAssignment Rules
  2. Check background workers are running:
    Terminal window
    ps aux | grep worker
  3. Review assignment rule conditions and priorities
  4. Ensure agents have proper roles and permissions
  5. Check logs for assignment errors

Additional Resources


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.