Fan-Out Notifications Demo

Open in v0

Broadcast an incident alert to Slack, Email, SMS, and PagerDuty in parallel using Promise.allSettled(). Each channel runs as a separate "use step" function with full retry semantics. The interactive UI lets you toggle channels between three failure modes to explore how the workflow handles partial failures.

How It Works

POST /api/fan-out
  → start(incidentFanOut, [incidentId, message, failures])
  → returns { runId }

GET /api/readable/[runId]
  → getRun(runId).getReadable() → SSE stream of ChannelEvent objects

Steps stream progress events via getWritable<ChannelEvent>() from inside "use step" functions. The client connects to the SSE stream and appends each event to an execution log in real time.

Files

FileRole
workflows/incident-fanout.tsWorkflow ("use workflow") + step functions ("use step")
app/api/fan-out/route.tsstart() from workflow/api — validates and enqueues
app/api/readable/[runId]/route.tsrun.getReadable() piped through SSE transform
app/page.tsxServer component — reads workflow source, builds line maps, highlights code
app/components/demo.tsxClient — SSE connection, state accumulator, execution log, controls
app/components/fanout-code-workbench.tsxTwo-pane code viewer with active line + gutter mark rendering

Failure Modes

The UI provides per-channel cycling buttons with three states:

ModeColorBackend Behavior
PassGrayChannel succeeds normally
Transient (T)AmberThrows Error on attempt 1 → SDK auto-retries → succeeds on attempt 2
Permanent (P)RedThrows FatalError → no retry → stays failed in Promise.allSettled()

This lets you demo both the SDK's automatic retry path and the Promise.allSettled() partial failure path in one run.

Channel Timing

ChannelSimulated LatencyTypical Completion Order
Slack650ms1st
PagerDuty750ms2nd
Email900ms3rd
SMS1150ms4th
Aggregate phase+500ms after last channel

State Machine

IDLE (no run)
  │ click "Dispatch Alert"
  ↓
FAN_OUT (tone: amber)
  │ Promise.allSettled() running all 4 channels in parallel
  │ Channels stream: channel_sending → channel_sent / channel_retrying / channel_failed
  ↓
AGGREGATING (tone: cyan)
  │ aggregateResults() counting successes and failures
  ↓
DONE (tone: green if all ok, red if any failed)
  │ Summary recorded, execution log complete
  ↓
IDLE (click "Reset Demo")

Execution Log

The execution log is append-only — each ChannelEvent from the workflow stream is timestamped and appended as it arrives. Entries are colored by outcome:

ColorMeaning
GrayNeutral (queued, sending, message)
GreenChannel sent successfully
AmberRetrying / retry succeeded
RedChannel permanently failed
CyanAggregation phase

Code Workbench Highlights

Active Line Highlighting

The current workflow phase determines which lines glow in the two-pane code viewer:

PhaseWorkflow PaneStep Pane
Fan-outPromise.allSettled() blockActive channel's step function
Aggregatingdeliveries + summary lines—
Done——

Gutter Marks

Lines get a full-width colored background + left border + icon based on outcome:

MarkColorIconPoints At
SuccessGreen✓ checkmarkreturn { providerId }
RetryAmber↻ circular arrowthrow new Error(...) (transient)
FailRed✗ crossthrow new FatalError(...) (permanent)

Marks fade in/out with a 500ms opacity transition, preserving their last shape via prevMarkRef.