Request Tracker (RT) is an enterprise-grade Perl-based help desk platform used by IT organizations, universities, and government agencies to manage tickets, change requests, and customer support workflows. When RT's mod_perl or FastCGI process pool exhausts under heavy load — common during an IT incident when every user submits a ticket simultaneously — the Apache server returns 503 errors and new ticket submissions fail; when RT's email gateway (rt-mailgate) cannot reach the RT application due to a misconfigured rtserver URL, inbound email addressed to support@your-org.example.com continues to be accepted by the mail server but silently discarded without creating tickets — users believe their request was submitted while the queue stalls; when RT's database (PostgreSQL or MySQL) develops query latency above a few hundred milliseconds, the session-heavy Mason template rendering causes cascading slowdowns that make RT appear hung, and active users are logged out as sessions time out. These failures disconnect your support operations from user requests without any automatic alerting unless external monitoring is in place.
Vigilmon gives you external visibility into RT's help desk health through HTTP probe monitoring and heartbeat monitors for email processing and SLA jobs. This tutorial covers both.
Why Request Tracker Needs External Monitoring
RT's failure modes span the entire support ticket lifecycle:
- Process pool exhaustion: RT's Perl-based application runs under Apache mod_perl or FastCGI; during incident spikes, concurrent ticket submissions from hundreds of users simultaneously exhaust the process pool — new requests queue and then time out with 503 errors, and users assume their support requests were submitted when they were not; RT's own dashboards are also affected because the same process pool serves the UI
- Email gateway silent failure: RT processes inbound email via
rt-mailgate, a CGI script that posts incoming email to the RT application; when thertserverURL inrt-mailgate's configuration points to a stale hostname after a server migration,rt-mailgatereceives a connection refused error and exits silently — the mail server delivers the message tort-mailgate,rt-mailgatereturns a non-zero exit code, and the mail server bounces the email or discards it depending on MTA configuration; users see no ticket created for their emailed request - Database performance degradation: RT is extremely database-intensive; every ticket view, queue listing, and saved search executes multiple SQL queries; when the database develops query latency (connection pool exhaustion, lock contention from a long-running report query, or bloated attachment storage) above ~300ms per query, Mason template rendering stacks these delays, causing page load times of 10–30 seconds; users report RT as "slow" or "down" when it is technically available but functionally unusable
- Attachment storage disk full: RT stores email attachments in the database by default (as BLOBs) or on disk depending on configuration; when the attachment storage fills — a single large incident thread with many attachments can consume gigabytes — new ticket updates that include attachments fail with internal server errors while text-only replies continue to work, creating an asymmetric failure that is difficult to diagnose without monitoring
- ScripsScripts failure: RT's Scrips automation engine triggers on ticket events to send notifications, enforce escalation policies, and update custom fields; when a Scrips subroutine throws a Perl exception due to a bug introduced in a custom Scrips update, the Scrip itself fails silently — notifications stop going out, but no error is surfaced in the UI and RT continues accepting tickets
- Outbound SMTP failure: RT sends ticket notification emails via SMTP; when the SMTP relay becomes unavailable (authentication failure after a password rotation, TLS certificate mismatch, or relay server restart), all outbound notifications from RT — new ticket confirmations, reply notifications, SLA escalations — fail to deliver; responders stop receiving ticket updates and believe queues are empty
External monitoring with Vigilmon adds:
- Proactive alerting when the RT application stops accepting HTTP connections
- Email gateway liveness through heartbeat monitors attached to email processing health checks
- Outbound SMTP validation via periodic test messages that ping a heartbeat on successful delivery
- Multi-region probe consensus that filters transient mod_perl GC pauses from genuine RT application crashes
Step 1: Build an RT Health Endpoint
RT does not expose a native /health endpoint. Build a thin health check using RT's REST API v2 (available in RT 5.x) or a lightweight sidecar that validates RT application and database health.
RT REST API Health Probe
RT 5.x provides a REST API v2 at /REST/2.0/. Probe it to verify the application is accepting connections:
# Test RT REST API liveness
curl -u 'rt-health-user:password' \
https://rt.your-org.example.com/REST/2.0/ \
-H 'Content-Type: application/json'
A healthy response returns {"_url":"https://rt.your-org.example.com/REST/2.0/","version":"5.0.x"}.
Node.js Health Sidecar
// health/request-tracker.js
const express = require('express');
const https = require('https');
const app = express();
const RT_HOST = process.env.RT_HOST || 'rt.your-org.example.com';
const RT_USER = process.env.RT_API_USER || 'rt-health';
const RT_PASS = process.env.RT_API_PASS || '';
function rtApiProbe() {
return new Promise((resolve, reject) => {
const auth = Buffer.from(`${RT_USER}:${RT_PASS}`).toString('base64');
const options = {
hostname: RT_HOST,
port: 443,
path: '/REST/2.0/',
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
rejectUnauthorized: false,
};
const req = https.request(options, res => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
try {
const data = JSON.parse(body);
return resolve({ version: data.version || 'unknown' });
} catch {
return reject(new Error('Invalid JSON from RT API'));
}
}
reject(new Error(`HTTP ${res.statusCode} from RT`));
});
});
req.on('error', reject);
req.setTimeout(8000, () => { req.destroy(); reject(new Error('RT API timeout')); });
req.end();
});
}
async function checkQueueDepth() {
// Query RT REST API v2 for open ticket count in critical queues
return new Promise((resolve, reject) => {
const auth = Buffer.from(`${RT_USER}:${RT_PASS}`).toString('base64');
const query = encodeURIComponent("Status='open' OR Status='new'");
const options = {
hostname: RT_HOST,
port: 443,
path: `/REST/2.0/tickets?query=${query}&fields=id,Subject,Status&per_page=1`,
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
rejectUnauthorized: false,
};
const req = https.request(options, res => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
const data = JSON.parse(body);
resolve({ open_ticket_count: data.total || 0 });
} catch {
reject(new Error('Invalid JSON from RT ticket query'));
}
});
});
req.on('error', reject);
req.setTimeout(8000, () => { req.destroy(); reject(new Error('Timeout')); });
req.end();
});
}
app.get('/health/rt', async (req, res) => {
const checks = {};
let healthy = true;
try {
const result = await rtApiProbe();
checks.rt_api = 'ok';
checks.rt_version = result.version;
} catch (err) {
checks.rt_api = `down: ${err.message}`;
healthy = false;
}
try {
const { open_ticket_count } = await checkQueueDepth();
checks.open_ticket_count = open_ticket_count;
} catch (err) {
checks.ticket_query = `error: ${err.message}`;
}
return res.status(healthy ? 200 : 503).json({
status: healthy ? 'ok' : 'down',
checks,
});
});
app.listen(3012, () => console.log('RT health sidecar on :3012'));
Python (FastAPI) Alternative
# health/rt_health.py
import os
import httpx
from fastapi import FastAPI, Response
app = FastAPI()
RT_HOST = os.environ.get("RT_HOST", "rt.your-org.example.com")
RT_USER = os.environ.get("RT_API_USER", "rt-health")
RT_PASS = os.environ.get("RT_API_PASS", "")
BASE_URL = f"https://{RT_HOST}"
@app.get("/health/rt")
async def rt_health():
checks = {}
healthy = True
async with httpx.AsyncClient(
verify=False,
timeout=8.0,
auth=(RT_USER, RT_PASS),
) as client:
try:
r = await client.get(f"{BASE_URL}/REST/2.0/")
r.raise_for_status()
data = r.json()
checks["rt_api"] = "ok"
checks["rt_version"] = data.get("version", "unknown")
except Exception as e:
checks["rt_api"] = f"down: {str(e)}"
healthy = False
try:
r = await client.get(
f"{BASE_URL}/REST/2.0/tickets",
params={"query": "Status='open' OR Status='new'", "per_page": 1},
)
r.raise_for_status()
data = r.json()
checks["open_ticket_count"] = data.get("total", 0)
except Exception as e:
checks["ticket_query"] = f"error: {str(e)}"
status_code = 200 if healthy else 503
return Response(
content=str({"status": "ok" if healthy else "down", "checks": checks}),
status_code=status_code,
media_type="application/json",
)
Step 2: Configure Vigilmon Monitoring
HTTP Monitor — RT Application
In your Vigilmon dashboard, create an HTTP monitor for the RT health endpoint:
| Field | Value |
|-------|-------|
| Monitor name | Request Tracker Application |
| URL | http://rt-sidecar.internal:3012/health/rt |
| Method | GET |
| Check interval | Every 1 minute |
| Expected status | 200 |
| Alert threshold | 2 consecutive failures |
| Regions | Select 2+ regions for consensus |
HTTP Monitor — RT Login Page
Also probe the RT web interface directly to catch cases where the application is responding to the API but the UI is broken:
| Field | Value |
|-------|-------|
| Monitor name | RT Web Interface |
| URL | https://rt.your-org.example.com/rt/ |
| Method | GET |
| Expected status | 200 |
| Response body must contain | Request Tracker |
| Check interval | Every 2 minutes |
Heartbeat Monitor — Email Gateway Processing
RT's email gateway (rt-mailgate) is the most critical hidden component: if it fails, tickets stop being created from email without any visible error. Set up a heartbeat monitor with a 30-minute timeout and ping it from a test email processing job:
#!/bin/bash
# /etc/cron.d/rt-email-gateway-health — runs every 15 minutes
# Send a test email to RT and verify it creates a ticket via the REST API
RT_HOST="rt.your-org.example.com"
RT_USER="rt-health"
RT_PASS="${RT_API_PASS}"
QUEUE="Health-Check" # A dedicated queue for health check tickets
HEARTBEAT_URL="https://vigilmon.online/hb/your-email-gateway-heartbeat"
# Create a test ticket via the REST API (bypassing email to isolate API health)
TICKET_ID=$(curl -su "${RT_USER}:${RT_PASS}" \
"https://${RT_HOST}/REST/2.0/ticket" \
-X POST \
-H 'Content-Type: application/json' \
-d "{\"Queue\":\"${QUEUE}\",\"Subject\":\"Health check $(date +%s)\",\"Status\":\"resolved\"}" \
2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null)
if [ -n "$TICKET_ID" ]; then
# Ticket created successfully — RT API is working
curl -fsS --max-time 10 "$HEARTBEAT_URL" > /dev/null 2>&1
fi
For testing actual email processing (catching rt-mailgate failures specifically):
#!/bin/bash
# /etc/cron.d/rt-mailgate-health — tests the actual email path every 30 minutes
SUBJECT="RT Health Check $(date +%s)"
RT_EMAIL="support@your-org.example.com"
# Send a test email through the real mail path
echo "Health check message body" | mail -s "$SUBJECT" "$RT_EMAIL"
# Wait 2 minutes for RT to process it, then check if a ticket was created
sleep 120
RT_HOST="rt.your-org.example.com"
FOUND=$(curl -su "rt-health:${RT_API_PASS}" \
"https://${RT_HOST}/REST/2.0/tickets?query=Subject+LIKE+'RT+Health+Check'&per_page=1" \
2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('total',0))" 2>/dev/null)
if [ "$FOUND" -gt 0 ] 2>/dev/null; then
curl -fsS --max-time 10 \
"https://vigilmon.online/hb/your-mailgate-heartbeat" > /dev/null 2>&1
fi
Configure this heartbeat with a 45-minute timeout to allow for the 2-minute email processing delay plus scheduling slack.
Heartbeat Monitor — Outbound SMTP
Monitor RT's outbound email notification delivery:
#!/bin/bash
# /etc/cron.d/rt-smtp-health — runs every 30 minutes
# Test SMTP delivery by sending a message and confirming receipt via IMAP or webhook
SMTP_HOST="${RT_SMTP_HOST:-localhost}"
TEST_RECIPIENT="monitoring+rt@your-org.example.com"
# Send test email via sendmail/postfix
echo "From: rt@your-org.example.com
To: ${TEST_RECIPIENT}
Subject: RT SMTP Health Check $(date +%s)
RT outbound SMTP health check." | sendmail -f rt@your-org.example.com "$TEST_RECIPIENT"
if [ $? -eq 0 ]; then
curl -fsS --max-time 10 \
"https://vigilmon.online/hb/your-smtp-heartbeat" > /dev/null 2>&1
fi
Step 3: Configure Alerting
Alert Policies
RT Application Alert
- Trigger: 2 consecutive failed HTTP probes from 2+ regions
- Severity: Critical
- Channels: PagerDuty + Slack #helpdesk-ops
- Message: "Request Tracker is unreachable. Ticket management is down. Users cannot submit or track support requests."
Email Gateway Alert
- Trigger: Heartbeat not received for 30 minutes
- Severity: Critical
- Channels: PagerDuty + Slack #helpdesk-ops
- Message: "RT email gateway heartbeat missed. Emails to support queues may not be creating tickets. Check rt-mailgate configuration and mail server delivery."
Outbound SMTP Alert
- Trigger: Heartbeat not received for 45 minutes
- Severity: High
- Channels: Slack #helpdesk-ops + email to RT admin
- Message: "RT outbound SMTP delivery missed. Ticket notifications may not be reaching responders. Check SMTP relay configuration."
SLA Breach Tracking
Use the RT REST API to check for tickets breaching their SLA and feed results to a heartbeat:
#!/bin/bash
# /etc/cron.d/rt-sla-check — check every 15 minutes
RT_HOST="rt.your-org.example.com"
# Query for open tickets that have breached their due date
BREACHED=$(curl -su "rt-health:${RT_API_PASS}" \
"https://${RT_HOST}/REST/2.0/tickets?query=Status!='resolved'+AND+Due+<+'now'&per_page=1" \
2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin).get('total',0))" 2>/dev/null)
echo "RT SLA breached tickets: $BREACHED"
# Alert via Vigilmon only if breach count is within acceptable range (<10)
if [ "$BREACHED" -lt 10 ] 2>/dev/null; then
curl -fsS --max-time 10 \
"https://vigilmon.online/hb/your-sla-heartbeat" > /dev/null 2>&1
fi
Configure the SLA heartbeat with a 20-minute timeout so any missed check or breach count spike triggers an alert.
Key Metrics Summary
| Metric | Alert Threshold | Impact | |--------|----------------|--------| | RT web application HTTP | Any failure | Ticket management inaccessible to all users | | RT email gateway | Heartbeat missing >30 min | Inbound email not creating tickets; requests silently lost | | Outbound SMTP delivery | Heartbeat missing >45 min | Responders not receiving ticket notifications | | Database query latency | >300ms p95 | RT UI becomes functionally unusable (10–30s page loads) | | Open ticket queue depth | Queue-specific thresholds | SLA breach risk for high-priority queues | | SLA breached ticket count | >10 open breaches | SLA compliance failure | | Attachment storage disk | >80% | Ticket updates with attachments failing | | Active session count | Sudden drop | Session store failure logging out all users |
Conclusion
Request Tracker's email gateway is its most critical hidden component — when rt-mailgate fails silently, users believe their requests have been filed while queues remain empty and SLAs drift. Vigilmon HTTP probes on the RT application catch availability failures immediately, while heartbeat monitors on email processing and outbound SMTP delivery give you coverage of the two failure modes most likely to go undetected for hours.
Configure the monitors in this tutorial and your help desk team will never again spend an hour investigating why tickets stopped coming in.