Health check monitoring extension for Zetian SMTP Server. Provides HTTP endpoints for liveness, readiness, and detailed health status monitoring with customizable checks, metrics, and Kubernetes integration support. Perfect for production monitoring and orchestration systems.
$ dotnet add package Zetian.HealthCheckA lightweight health monitoring extension for Zetian SMTP Server, providing HTTP endpoints for health checks, liveness probes, readiness checks, and custom health metrics. Perfect for Kubernetes, Docker Swarm, load balancers, and monitoring systems.
/health, /health/livez, /health/readyz# Install Zetian SMTP Server (required)
dotnet add package Zetian
# Install Health Check Extension
dotnet add package Zetian.HealthCheck
using Zetian.Server;
using Zetian.HealthCheck.Extensions;
// Create and start SMTP server
var server = SmtpServerBuilder.CreateBasic();
await server.StartAsync();
// Enable health check on port 8080
var healthCheck = server.EnableHealthCheck(8080);
await healthCheck.StartAsync();
// Health check now available at:
// http://localhost:8080/health
// http://localhost:8080/health/livez
// http://localhost:8080/health/readyz
// Start both SMTP and health check together
var healthCheck = await server.StartWithHealthCheckAsync(8080);
// Or with custom health checks configured inline
await server.StartWithHealthCheckAsync(8080, healthService =>
{
// Add custom health checks
healthService.AddHealthCheck("database", async (ct) =>
{
try
{
await CheckDatabaseAsync();
return HealthCheckResult.Healthy("Database connected");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database unavailable", ex);
}
});
healthService.AddHealthCheck("redis", async (ct) =>
{
try
{
await CheckRedisAsync();
return HealthCheckResult.Healthy("Redis connected");
}
catch (Exception ex)
{
// Redis is not critical, mark as degraded
return HealthCheckResult.Degraded("Redis unavailable", ex);
}
});
});
// Custom port
var healthCheck = server.EnableHealthCheck(9090);
// Custom path (must end with /)
var healthCheck = server.EnableHealthCheck(8080, "/status/");
// Bind to all interfaces (accessible externally)
var healthCheck = server.EnableHealthCheck("0.0.0.0", 8080);
// Bind to hostname
var healthCheck = server.EnableHealthCheck("myserver.local", 8080);
// IPv6 support
var healthCheck = server.EnableHealthCheck(IPAddress.IPv6Loopback, 8080);
// Bind to specific IP
var healthCheck = server.EnableHealthCheck(IPAddress.Parse("192.168.1.100"), 8080);
using Zetian.Server;
using Zetian.HealthCheck.Models;
using Zetian.HealthCheck.Extensions;
var healthCheck = server.EnableHealthCheck(8080);
// Add database check
healthCheck.AddHealthCheck("database", async (ct) =>
{
try
{
var isConnected = await CheckDatabaseAsync();
return isConnected
? HealthCheckResult.Healthy("Database connected")
: HealthCheckResult.Unhealthy("Database connection failed");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database error", ex);
}
});
// Add disk space check
healthCheck.AddHealthCheck("disk_space", async (ct) =>
{
var drive = new DriveInfo("C:\\");
var freePercent = (double)drive.AvailableFreeSpace / drive.TotalSize * 100;
var data = new Dictionary<string, object>
{
["freePercent"] = Math.Round(freePercent, 2),
["totalSpaceGB"] = drive.TotalSize / (1024 * 1024 * 1024),
["freeSpaceGB"] = drive.AvailableFreeSpace / (1024 * 1024 * 1024)
};
if (freePercent < 10)
return HealthCheckResult.Unhealthy($"Critical: {freePercent:F2}% free", data: data);
if (freePercent < 20)
return HealthCheckResult.Degraded($"Low space: {freePercent:F2}% free", data: data);
return HealthCheckResult.Healthy($"Disk space OK: {freePercent:F2}% free", data: data);
});
await healthCheck.StartAsync();
using Zetian.Server;
using Zetian.HealthCheck.Options;
using Zetian.HealthCheck.Extensions;
// Custom thresholds for SMTP health
var smtpOptions = new SmtpHealthCheckOptions
{
CheckMemoryUsage = true, // Include memory metrics
DegradedThresholdPercent = 60, // Degraded at 60% utilization
UnhealthyThresholdPercent = 85 // Unhealthy at 85% utilization
};
// Custom service options
var serviceOptions = new HealthCheckServiceOptions
{
Prefixes = new() { "http://+:8080/health/" }, // Listen on all interfaces
DegradedStatusCode = 218 // Custom HTTP status for degraded (default: 200)
};
var healthCheck = server.EnableHealthCheck(serviceOptions, smtpOptions);
await healthCheck.StartAsync();
{
"status": "Healthy",
"timestamp": "2024-10-22T16:30:00Z",
"checks": {
"smtp_server": {
"status": "Healthy",
"description": "SMTP server is healthy",
"data": {
"status": "running",
"uptime": "2d 3h 15m 42s",
"activeSessions": 5,
"maxSessions": 50,
"utilizationPercent": 10.0,
"memoryUsageMB": 45.2
}
},
"database": {
"status": "Healthy",
"description": "Database connected"
},
"disk_space": {
"status": "Healthy",
"description": "Disk space OK: 65.3% free",
"data": {
"freeSpaceGB": 250,
"totalSpaceGB": 500,
"freePercent": 50.0
}
}
}
}
{
"status": "Degraded",
"timestamp": "2024-10-22T16:30:00Z",
"checks": {
"smtp_server": {
"status": "Degraded",
"description": "SMTP server utilization is high",
"data": {
"activeSessions": 35,
"maxSessions": 50,
"utilizationPercent": 70.0
}
}
}
}
{
"status": "Unhealthy",
"timestamp": "2024-10-22T16:30:00Z",
"checks": {
"smtp_server": {
"status": "Unhealthy",
"description": "SMTP server is not running"
}
}
}
apiVersion: apps/v1
kind: Deployment
metadata:
name: smtp-server
spec:
replicas: 3
selector:
matchLabels:
app: smtp-server
template:
metadata:
labels:
app: smtp-server
spec:
containers:
- name: smtp-server
image: myregistry/smtp-server:latest
ports:
- containerPort: 25 # SMTP port
- containerPort: 8080 # Health check port
livenessProbe:
httpGet:
path: /health/livez
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/readyz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
apiVersion: v1
kind: Service
metadata:
name: smtp-service
spec:
selector:
app: smtp-server
ports:
- name: smtp
port: 25
targetPort: 25
- name: health
port: 8080
targetPort: 8080
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY . .
EXPOSE 25 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["dotnet", "MySmtpServer.dll"]
services:
smtp:
image: mysmtp:latest
ports:
- "25:25"
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
using Zetian.Server;
using Zetian.HealthCheck.Models;
using Zetian.HealthCheck.Extensions;
// Add custom metrics check for Prometheus
healthCheck.AddHealthCheck("metrics", async (ct) =>
{
var data = new Dictionary<string, object>
{
["smtp_server_running"] = server.IsRunning,
["smtp_server_port"] = server.Configuration.Port,
["smtp_max_connections"] = server.Configuration.MaxConnections,
["smtp_max_message_size"] = server.Configuration.MaxMessageSize
};
// Calculate uptime if server is running
if (server.IsRunning && server.StartTime.HasValue)
{
var uptime = DateTime.UtcNow - server.StartTime.Value;
data["smtp_uptime_seconds"] = uptime.TotalSeconds;
data["smtp_uptime_formatted"] = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m";
}
// Add configuration metrics
if (server.Configuration.RequireAuthentication)
{
data["smtp_auth_required"] = true;
}
return HealthCheckResult.Healthy("Metrics collected", data: data);
});
Use the JSON endpoints to collect metrics:
# Collect health data
curl http://localhost:8080/health | jq '.checks.smtp_server.data'
# Check liveness
curl -f http://localhost:8080/health/livez
# Check readiness
curl -f http://localhost:8080/health/readyz
| Method | Description |
|---|---|
EnableHealthCheck(port) | Enable health check on localhost |
AddHealthCheck(name, checkFunc) | Add custom health check to service |
StartWithHealthCheckAsync(port) | Start server and health check |
EnableHealthCheck(hostname, port) | Enable on specific hostname |
EnableHealthCheck(IPAddress, port) | Enable on specific IP |
EnableHealthCheck(options, healthOptions) | Advanced configuration |
StartWithHealthCheckAsync(hostname, port) | Start on specific hostname |
StartWithHealthCheckAsync(IPAddress, port) | Start on specific IP |
StartWithHealthCheckAsync(port, configureHealthChecks) | Start server with custom health checks |
StartWithHealthCheckAsync(hostname, port, configureHealthChecks) | Start on hostname with custom checks |
StartWithHealthCheckAsync(IPAddress, port, configureHealthChecks) | Start on IP with custom checks |
| Status | HTTP Code | Description |
|---|---|---|
| Healthy | 200 | Everything is working |
| Degraded | 200/218 | Working but with issues |
| Unhealthy | 503 | Critical problems detected |
Port Already in Use
// Check if port is available before starting
var listener = new HttpListener();
try
{
listener.Prefixes.Add("http://localhost:8080/");
listener.Start();
listener.Stop();
// Port is available
}
catch (HttpListenerException)
{
// Port is in use
}
Access Denied on Non-Localhost
# Windows: Run as administrator or add URL ACL
netsh http add urlacl url=http://+:8080/health/ user=Everyone
# Linux: Use authbind or run as root
sudo setcap cap_net_bind_service=+ep /usr/bin/dotnet
IPv6 Issues
// Check IPv6 support
if (Socket.OSSupportsIPv6)
{
var healthCheck = server.EnableHealthCheck(IPAddress.IPv6Any, 8080);
}
else
{
var healthCheck = server.EnableHealthCheck("0.0.0.0", 8080);
}
Best Practices:
Report Security Issues: taiizor@vegalya.com
MIT License - see LICENSE
Built with ❤️ for the .NET community