Who this is for
Product Manager: You need to see how outbound delivery feedback drives workflow progression — "if the provider says delivered, resume; if bounced, stop" — with proof on the delivery trace timeline.
Feature Developer: You author a notifier with an optional workflow/2 callback and wire webhook ingress so provider feedback becomes durable signals.
Prerequisites
- Multi-step journeys — full
workflow/2authoring with progress rule kinds - Golden Path — trigger baseline and webhook feedback loop appendix
Feature Developer: notifier with workflow
Workflows are declared via the optional workflow/2 callback on a Chimeway.Notifier module — not a separate workflow behaviour module.
Minimal envelope shape (progress arrays abbreviated — see the journey guide for full rules):
{:ok, %{
workflow_key: "mention_escalation",
workflow_version: 1,
steps: [
%{step_key: "in_app", step_order: 1, channel: "in_app", config: %{"progress" => [...]}},
%{step_key: "email", step_order: 2, channel: "email", config: %{"progress" => [...]}}
]
}}Progress rule kinds include wait_until, on_outcome, and stop. See Multi-step journeys for on_outcome outcome vocabulary and full mention-escalation example.
Trigger the notifier:
Chimeway.trigger(
MyApp.Notifiers.MentionEscalation,
%{actor_id: "user:42", mention_id: "mention-99"},
idempotency_key: "mention-99-escalation",
tenant_id: "org_456"
)End-to-end feedback loop
- Host triggers notifier → delivery planned and dispatched
- Adapter sends (or simulates) outbound delivery
- Provider POSTs webhook to a host route (demo host uses
/webhooks/chimeway/echo— configure your own ingress, do not copy a full controller here) Chimeway.Webhooks.ProcessFeedbackWorkernormalizes feedback and records attempts- Worker emits
Chimeway.Signal.track(tenant_id, actor_id, "chimeway.delivery.succeeded", %{...})— argument order is tenant, actor, event name, payload Chimeway.Dispatch.SignalRouterWorkerdrains the signal job →Chimeway.Workflows.route_signal/1matches waiting runsChimeway.Traces.explain_delivery/1shows:webhook_receivedand/or workflow stop/resume events on the timeline
Canonical delivery signal event names: chimeway.delivery.succeeded, chimeway.delivery.bounced, chimeway.delivery.failed.
Progress path: delivered signal resumes the run
When the provider reports success:
ProcessFeedbackWorkerwrites a:succeededattempt and trackschimeway.delivery.succeeded.SignalRouterWorkerroutes the signal; a:waitingrun whosepending_signalsincludes that event resumes to:active.- The trace timeline includes
:webhook_receivedwithdetail.signal_event_name == "chimeway.delivery.succeeded".
This matches the demo host progress describe block in the E2E test.
Stop path: bounced signal stops the workflow
When the provider reports a bounce:
ProcessFeedbackWorkerrecords a:bouncedattempt and trackschimeway.delivery.bounced.- Progression evaluates a
stoprule and sets the workflow run to:stopped. - The trace timeline includes
:webhook_receivedand:workflow_stoppedfor the same delivery.
On the stop path the run is already :stopped before signal routing completes — that is expected.
Inspecting progression in the trace
{:ok, %{timeline: timeline}} = Chimeway.Traces.explain_delivery(delivery_id)
Enum.map(timeline, & &1.event)Look for :webhook_received, signal-driven resume (run returns to :active), or :workflow_stopped depending on feedback outcome.
Runnable proof in the repo
- Feedback pipeline E2E test — progress and stop describe blocks with real webhook POST + Oban drain
- Golden path webhook appendix — outcome summary and links
Related guides
- Multi-step journeys — workflow authoring reference
- Mention escalation — inbox-read / PM JTBD path (not delivery-feedback webhooks)
- Golden Path — first trigger and webhook loop
- Tracing a Notification — telemetry and correlation depth