Structured explanation of a single delivery — the primary operator debugging artifact.
Returned by Chimeway.Traces.explain_delivery/1. Contains the full context
needed to answer "why was this delivery suppressed/failed/succeeded?" without
requiring additional queries.
Fields
delivery_id— UUID of the delivery rowevent_id— UUID of the parent eventcorrelation_id— host-app correlation string (request_id, trace_id), or nilnotification_key— stable notification type identifierrecipient_id— recipient identity stringchannel— delivery channel string (for example "in_app", "email", "webhook_partner")render_key— stable per-channel render identity persisted on the delivery row, or nilrender_version— stable per-channel render version persisted on the delivery row, or nilstatus— final delivery status: :succeeded | :failed | :suppressed | :pending | :cancelledplanning_reason— orchestration/planning reason when the delivery is intentionally held, else nilplanning_context— sanitized persisted planning facts for explainability, else nilnext_eligible_at— UTC timestamp for the next dispatchable moment when deferred, else nilresume_source— sanitized scheduler/source label when a deferred row later resumes, else nilresume_scheduled_at— original UTC time the deferred row was scheduled to resume, else nilresumed_at— UTC timestamp when the canonical delivery row left deferred state, else nilsuppression_reason— reason atom string when status is:suppressedOR:cancelled, else nil. The four documented reason strings are:"channel_disabled"— set when status is:suppressed(policy preference blocked the channel)"retries_exhausted"— set when status is:cancelled(Oban exhausted max_attempts on transient failures, REL-03 D-10/D-11)"permanent_failure"— set when status is:cancelled(adapter returned a permanent error)"bounced"— set when status is:cancelled(adapter returned a bounce)
last_attempt— map with :outcome, :inserted_at, :attempt_number, :error_class, :adapter_module for the most recent attempt, or nil.:adapter_moduleis nil for pre-Phase-29 attempts.digest— digest-specific reasoning for source or emitted digest rows, else niltimeline— chronological list of lifecycle events, each a map with :at, :event, :detail
Summary
Types
@type t() :: %Chimeway.Traces.Explanation{ channel: String.t(), correlation_id: String.t() | nil, delivery_id: String.t(), digest: map() | nil, event_id: String.t(), last_attempt: %{ outcome: atom(), inserted_at: DateTime.t(), attempt_number: pos_integer() | nil, error_class: String.t() | nil, adapter_module: String.t() | nil } | nil, next_eligible_at: DateTime.t() | nil, notification_key: String.t(), planning_context: map() | nil, planning_reason: String.t() | nil, recipient_id: String.t(), render_key: String.t() | nil, render_version: pos_integer() | nil, resume_scheduled_at: DateTime.t() | nil, resume_source: String.t() | nil, resumed_at: DateTime.t() | nil, status: :succeeded | :failed | :suppressed | :pending | :cancelled | :dispatched, suppression_reason: String.t() | nil, timeline: [timeline_entry()] }
@type timeline_entry() :: %{at: DateTime.t(), event: atom(), detail: map()}