Skip to content

Telemetry

Relate Mail uses OpenTelemetry for distributed tracing and metrics collection across all backend services. The telemetry system provides visibility into protocol command processing, authentication patterns, message delivery, and database operations.

TelemetryConfiguration

The TelemetryConfiguration class provides a centralized setup method and shared activity sources for all protocol servers.

Activity Sources

Activity sources create distributed tracing spans for protocol operations:

SourceNameUsed By
SmtpActivitySourceRelate.SmtpSMTP server
Pop3ActivitySourceRelate.Pop3POP3 server
ImapActivitySourceRelate.ImapIMAP server
ApiActivitySourceRelate.ApiREST API

Each protocol handler creates per-command activities. For example, the POP3 handler creates a pop3.command.retr activity for every RETR command, with tags for the session ID and command name.

Registration

AddRelateTelemetry is an extension method on IServiceCollection that configures OpenTelemetry tracing and metrics:

csharp
services.AddRelateTelemetry(
    configuration,
    serviceName: "relate-pop3",
    configureTracing: tracing => tracing.AddAspNetCoreInstrumentation(),
    configureMetrics: metrics => metrics.AddAspNetCoreInstrumentation()
);

The method:

  1. Configures the OpenTelemetry resource with the service name and version.
  2. Registers all four activity sources for tracing.
  3. Adds Entity Framework Core instrumentation (including SQL statement capture).
  4. Allows the caller to add additional instrumentation (e.g., ASP.NET Core, HTTP client).
  5. Registers all three protocol meters for metrics.
  6. Configures the exporter -- OTLP if an endpoint is configured, console otherwise.

OTLP Endpoint

The exporter endpoint is configured via:

Otel__Endpoint=http://otel-collector:4317

When set, both traces and metrics are exported to the OTLP endpoint. When not set, traces are exported to the console (useful for development).

ProtocolMetrics

ProtocolMetrics defines all protocol-level metrics using the .NET System.Diagnostics.Metrics API.

SMTP Metrics

MetricTypeUnitDescription
smtp.messages.receivedCountermessagesTotal messages received via SMTP
smtp.bytes.receivedCounterbytesTotal bytes received via SMTP
smtp.auth.attemptsCounterattemptsSMTP authentication attempts
smtp.auth.failuresCounterfailuresSMTP authentication failures
smtp.message.processing.durationHistogrammsTime to process an SMTP message
smtp.connections.activeUpDownCounterconnectionsCurrently active SMTP connections

POP3 Metrics

MetricTypeUnitDescription
pop3.messages.retrievedCountermessagesTotal messages retrieved via POP3
pop3.bytes.sentCounterbytesTotal bytes sent via POP3
pop3.auth.attemptsCounterattemptsPOP3 authentication attempts
pop3.auth.failuresCounterfailuresPOP3 authentication failures
pop3.commandsCountercommandsPOP3 commands processed (tagged by command)
pop3.sessions.activeUpDownCountersessionsCurrently active POP3 sessions

IMAP Metrics

MetricTypeUnitDescription
imap.messages.retrievedCountermessagesTotal messages retrieved via IMAP
imap.bytes.sentCounterbytesTotal bytes sent via IMAP
imap.auth.attemptsCounterattemptsIMAP authentication attempts
imap.auth.failuresCounterfailuresIMAP authentication failures
imap.commandsCountercommandsIMAP commands processed (tagged by command)
imap.sessions.activeUpDownCountersessionsCurrently active IMAP sessions

Meter Names

Each protocol has its own meter:

MeterName
SMTPRelate.Smtp
POP3Relate.Pop3
IMAPRelate.Imap

Per-Command Tracking

Every protocol command is wrapped in an OpenTelemetry activity that captures:

TagDescription
{protocol}.session_idSession connection ID
{protocol}.commandCommand name (e.g., RETR, FETCH)
{protocol}.stateCurrent session state (IMAP only)

On error, additional tags are added:

TagDescription
exception.typeException type name
exception.messageException message

The activity status is set to Error on failure.

Authentication Tracing

The ProtocolAuthenticator creates an activity for each authentication attempt:

TagDescription
{protocol}.auth.userUsername (email address)
{protocol}.auth.cache_hitWhether the result was served from cache
{protocol}.auth.successWhether authentication succeeded
{protocol}.auth.rate_limitedWhether the request was rate-limited
{protocol}.auth.failure_reasonReason for failure (user_not_found, invalid_key, missing_scope)
{protocol}.auth.key_nameName of the matched API key (on success)

Consuming Telemetry

Development

Without an OTLP endpoint configured, traces are written to the console. This is useful for debugging protocol interactions.

Production with OTLP

Configure an OpenTelemetry Collector endpoint:

bash
Otel__Endpoint=http://otel-collector:4317

The collector can then forward data to backends like:

  • Jaeger or Tempo for distributed tracing
  • Prometheus for metrics
  • Grafana for dashboards

Example: Grafana Dashboard

Useful queries for a Grafana dashboard:

  • Authentication failure rate: rate(smtp_auth_failures_total[5m]) / rate(smtp_auth_attempts_total[5m])
  • Active sessions: pop3_sessions_active / imap_sessions_active
  • Command throughput: rate(pop3_commands_total[5m]) grouped by command tag
  • Message retrieval rate: rate(imap_messages_retrieved_total[5m])

Example: Docker Compose with Jaeger

yaml
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"  # Jaeger UI
      - "4317:4317"    # OTLP gRPC

  api:
    environment:
      - Otel__Endpoint=http://jaeger:4317

Released under the MIT License.