guides/recipes/tracing-a-notification.md

# Tracing a Notification

Chimeway emits standard `:telemetry` events at every stage of the notification lifecycle: triggering, policy evaluation, delivery planning, and actual delivery attempts. You can hook into these events to emit logs, record metrics, or debug why a notification wasn't sent.

## Core Principles of Chimeway Telemetry

1. **Safety First**: Chimeway intentionally does **not** include the notification payload in telemetry metadata. This prevents accidentally leaking PII, passwords, or sensitive financial data into logs or APM systems.
2. **Correlation IDs**: To trace a notification through its lifecycle, rely on safe correlation IDs instead of payload data. Every step of the pipeline includes a consistent `correlation_id` and the original `notification_key`.

## Available Telemetry Events

Chimeway emits the following key events (each has a `:start`, `:stop`, and `:exception` variant):

- `[:chimeway, :deliveries, :plan, *]` - Emitted when a notification is triggered and a delivery plan is created.
- `[:chimeway, :policy, :evaluate, *]` - Emitted when policy rules (quiet hours, frequency caps) are evaluated.
- `[:chimeway, :delivery, :attempt, *]` - Emitted for each adapter delivery attempt (e.g., sending the actual email).
- `[:chimeway, :oban, :worker, *]` - Emitted by the Oban worker during async dispatch.

## Attaching to Telemetry Events

To trace deliveries, you can create a module that attaches to these events. Here is an example of a simple logger that listens to the `[:chimeway, :delivery, :attempt, :stop]` event to see whether an attempt succeeded or failed.

```elixir
defmodule MyApp.ChimewayLogger do
  require Logger

  def setup do
    :telemetry.attach(
      "chimeway-logger-delivery-stop",
      [:chimeway, :delivery, :attempt, :stop],
      &__MODULE__.handle_event/4,
      nil
    )
  end

  def handle_event(_event, measurements, metadata, _config) do
    # ⚠️ Notice that `metadata` only contains safe correlation IDs and state.
    # The actual payload is intentionally omitted.
    delivery_id = metadata[:delivery_id]
    correlation_id = metadata[:correlation_id]
    notification_key = metadata[:notification_key]
    status = metadata[:status]
    
    Logger.info(
      "Delivery Attempt Finished [#{status}] " <>
      "delivery_id=#{delivery_id} " <>
      "correlation_id=#{correlation_id} " <>
      "key=#{notification_key} " <>
      "duration=#{measurements.duration}"
    )
  end
end
```

To enable this, call `MyApp.ChimewayLogger.setup()` in your application's start function (e.g., in `application.ex`).

## Diagnosing "Why wasn't this sent?"

If you're using IEx and need to know why a specific notification was suppressed, you can use the built-in `Chimeway.Traces` module. The traces provide an explainable view of the delivery's policy evaluations.

```elixir
# In IEx
alias Chimeway.Traces

# If you have the delivery_id:
Traces.explain_delivery(delivery_id)

# If you only have the correlation_id (useful if you found it in your logs!):
Traces.find_traces_by_correlation_id(correlation_id)
```

The trace will explicitly tell you if a notification was blocked by a frequency cap, suppressed during quiet hours, or rejected due to opt-out preferences.