Skip to content

Prometheus Configuration

This guide covers the full Prometheus configuration for monitoring every Mailyte component — mail services, workers, databases, and system resources.

Full Configuration

YAML
# monitoring/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  scrape_timeout: 10s

  external_labels:
    cluster: "mailyte-production"
    environment: "production"

rule_files:
  - "alerts/*.yml"
  - "recording_rules/*.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

scrape_configs:
  # ==========================================================
  # Mailyte Services
  # ==========================================================

  - job_name: "mailyte-api"
    metrics_path: /metrics
    scrape_interval: 10s
    static_configs:
      - targets: ["api:8080"]
        labels:
          service: "api"
          component: "core"

  - job_name: "mailyte-tracking"
    metrics_path: /metrics
    static_configs:
      - targets: ["tracking:8086"]
        labels:
          service: "tracking"
          component: "worker"

  - job_name: "mailyte-webhooks"
    metrics_path: /metrics
    static_configs:
      - targets: ["webhooks:8081"]
        labels:
          service: "webhooks"
          component: "worker"

  - job_name: "mailyte-analytics"
    metrics_path: /metrics
    static_configs:
      - targets: ["analytics:8082"]
        labels:
          service: "analytics"
          component: "worker"

  - job_name: "mailyte-rate-limiter"
    metrics_path: /metrics
    static_configs:
      - targets: ["rate-limiter:8084"]
        labels:
          service: "rate-limiter"
          component: "worker"

  - job_name: "mailyte-queue-manager"
    metrics_path: /metrics
    scrape_interval: 10s
    static_configs:
      - targets: ["queue-manager:8085"]
        labels:
          service: "queue-manager"
          component: "worker"

  - job_name: "mailyte-storage"
    metrics_path: /metrics
    static_configs:
      - targets: ["storage-usage:8087"]
        labels:
          service: "storage"
          component: "worker"

  - job_name: "mailyte-monitoring"
    metrics_path: /metrics
    static_configs:
      - targets: ["monitoring:8088"]
        labels:
          service: "monitoring"
          component: "worker"

  - job_name: "mailyte-rag"
    metrics_path: /metrics
    static_configs:
      - targets: ["rag:8089"]
        labels:
          service: "rag"
          component: "worker"

  # ==========================================================
  # Mail Services
  # ==========================================================

  - job_name: "rspamd"
    metrics_path: /metrics
    scrape_interval: 30s
    static_configs:
      - targets: ["rspamd:11334"]
        labels:
          service: "rspamd"
          component: "mail"

  # Postfix metrics via postfix-exporter (if installed)
  - job_name: "postfix"
    metrics_path: /metrics
    scrape_interval: 30s
    static_configs:
      - targets: ["postfix-exporter:9154"]
        labels:
          service: "postfix"
          component: "mail"

  # ==========================================================
  # Data Stores
  # ==========================================================

  - job_name: "mysql"
    metrics_path: /metrics
    scrape_interval: 30s
    static_configs:
      - targets: ["mysql-exporter:9104"]
        labels:
          service: "mysql"
          component: "database"

  - job_name: "redis"
    metrics_path: /metrics
    scrape_interval: 15s
    static_configs:
      - targets: ["redis-exporter:9121"]
        labels:
          service: "redis"
          component: "database"

  # ==========================================================
  # System
  # ==========================================================

  - job_name: "node"
    metrics_path: /metrics
    static_configs:
      - targets: ["node-exporter:9100"]
        labels:
          service: "host"
          component: "system"

  # Prometheus self-monitoring
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

Optional Exporters

To get metrics from MySQL and Redis, add these exporters:

YAML
# docker-compose.monitoring.yml
services:
  mysql-exporter:
    image: prom/mysqld-exporter:latest
    container_name: mysql-exporter
    environment:
      DATA_SOURCE_NAME: "${DB_USER:-mailuser}:${DB_PASSWORD:-mailpassword}@tcp(mysql:3306)/${DB_NAME:-mailserver}"
    ports:
      - "9104:9104"
    depends_on:
      mysql:
        condition: service_healthy
    networks:
      - mailserver_network

  redis-exporter:
    image: oliver006/redis_exporter:latest
    container_name: redis-exporter
    environment:
      REDIS_ADDR: "redis://redis:6379"
    ports:
      - "9121:9121"
    networks:
      - mailserver_network

  postfix-exporter:
    image: kumina/postfix-exporter:latest
    container_name: postfix-exporter
    ports:
      - "9154:9154"
    volumes:
      - ./logs/mailer/postfix:/var/log/postfix:ro
    command:
      - '--postfix.logfile_path=/var/log/postfix/maillog'
    networks:
      - mailserver_network

Recording Rules

Pre-compute expensive queries for faster dashboards:

YAML
# monitoring/prometheus/recording_rules/mailyte.yml
groups:
  - name: mailyte_recording_rules
    interval: 30s
    rules:
      # Email throughput per minute
      - record: mailyte:emails_sent:rate5m
        expr: rate(mailyte_emails_sent_total[5m]) * 60

      - record: mailyte:emails_received:rate5m
        expr: rate(mailyte_emails_received_total[5m]) * 60

      # Bounce rate
      - record: mailyte:bounce_rate:5m
        expr: >
          rate(mailyte_emails_bounced_total[5m])
          / rate(mailyte_emails_sent_total[5m])

      # API request rate
      - record: mailyte:api_requests:rate5m
        expr: rate(mailyte_api_requests_total[5m]) * 60

      # API error rate
      - record: mailyte:api_error_rate:5m
        expr: >
          rate(mailyte_api_requests_total{status=~"5.."}[5m])
          / rate(mailyte_api_requests_total[5m])

      # Average API latency
      - record: mailyte:api_latency:avg5m
        expr: >
          rate(mailyte_api_request_duration_seconds_sum[5m])
          / rate(mailyte_api_request_duration_seconds_count[5m])

      # Queue depth trend
      - record: mailyte:queue_depth:avg5m
        expr: avg_over_time(mailyte_mail_queue_size[5m])

Alert Rules

YAML
# monitoring/prometheus/alerts/mailyte.yml
groups:
  - name: mailyte_service_alerts
    rules:
      - alert: MailyteServiceDown
        expr: up{component="worker"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Mailyte service {{ $labels.service }} is down"
          description: "The {{ $labels.service }} service has been unreachable for more than 1 minute."

      - alert: MailQueueBacklog
        expr: mailyte_mail_queue_size > 500
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Mail queue backlog: {{ $value }} messages"

      - alert: MailQueueCritical
        expr: mailyte_mail_queue_size > 5000
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Critical mail queue backlog: {{ $value }} messages"

      - alert: HighBounceRate
        expr: mailyte:bounce_rate:5m > 0.05
        for: 15m
        labels:
          severity: critical
        annotations:
          summary: "Bounce rate is {{ $value | humanizePercentage }}"

      - alert: APIHighLatency
        expr: mailyte:api_latency:avg5m > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "API average latency is {{ $value }}s"

      - alert: APIHighErrorRate
        expr: mailyte:api_error_rate:5m > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "API error rate is {{ $value | humanizePercentage }}"

  - name: mailyte_infrastructure_alerts
    rules:
      - alert: HighDiskUsage
        expr: >
          (1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Disk usage is {{ $value | humanizePercentage }}"

      - alert: HighMemoryUsage
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Memory usage is {{ $value | humanizePercentage }}"

      - alert: MySQLConnectionsHigh
        expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL connections at {{ $value | humanizePercentage }} of max"

      - alert: RedisMemoryHigh
        expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Redis memory at {{ $value | humanizePercentage }} of max"

      - alert: SSLCertExpiringSoon
        expr: mailyte_ssl_cert_expiry_days < 14
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "SSL cert for {{ $labels.domain }} expires in {{ $value }} days"

      - alert: SSLCertExpiryCritical
        expr: mailyte_ssl_cert_expiry_days < 3
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "SSL cert for {{ $labels.domain }} expires in {{ $value }} days"

Storage and Retention

Disk Usage Estimation

Prometheus uses about 1-2 bytes per sample. With the config above:

  • ~50 metrics per service x 10 services = 500 metrics
  • 15s scrape interval = 4 samples/minute per metric
  • 500 x 4 x 60 x 24 = ~2.9M samples/day
  • ~5-6 MB/day of storage

At 30 days retention, you'll use about 150-180 MB.

Adjust Retention

YAML
# In prometheus service command
command:
  - '--storage.tsdb.retention.time=30d'    # Keep 30 days
  - '--storage.tsdb.retention.size=5GB'    # Or cap at 5GB

Verifying the Setup

Bash
# Check Prometheus targets
curl -s http://localhost:9090/api/v1/targets | python3 -m json.tool | grep -E '"state"|"job"'

# Check that metrics are flowing
curl -s http://localhost:9090/api/v1/query?query=up | python3 -m json.tool

# Check alert rules are loaded
curl -s http://localhost:9090/api/v1/rules | python3 -m json.tool | head -50

All targets should show state: "up". If any show "down", check that the service is running and the port is accessible within the Docker network.