- Temporal
- /
- Redis pub/sub limitations for Temporal frontend delivery
Redis pub/sub limitations for Temporal frontend delivery
TL;DR Redis pub/sub is fire-and-forget. Events published while a client is disconnected are gone. For single-device, always-connected use cases, Redis pub/sub delivers tokens reliably enough. Any reconnect, second device, or offline notification scenario needs a delivery layer with replay.
Redis pub/sub is usually the first pattern that teams reach for when they need to stream Temporal workflow state to a frontend. The workflow publishes tokens to a Redis channel, and a relay server subscribes and forwards them to SSE endpoints that the browser holds open. For a client that stays connected throughout, this setup works well.
The gap appears the moment a client disconnects. And in production, clients disconnect constantly.
Key takeaways
Redis pub/sub delivers messages only to subscribers connected at publish time: anything published during a disconnect is gone for good.
Workflow Streams closes the reconnect gap for a single device (Python, and TypeScript from 17 June 2026), but a second device joining mid-run can't retrieve the events that it missed.
Closing the reconnect and multi-device gap needs a delivery layer that buffers events and replays them from each client's last position. Ably AI Transport ships this as a documented integration today.
How does the Temporal-to-Redis-to-SSE pattern work?
A Temporal activity completes and publishes the result to a Redis channel. A relay service subscribes to that channel and forwards messages to SSE endpoints that the browser holds open. The workflow doesn't know or care how events reach the client. This is one of several approaches that teams reach for when building a delivery layer alongside Temporal.
See [Why Temporal workflows need a frontend delivery layer](https://ably.com/temporal) for the full picture of why Temporal doesn't solve this on its own.
The pattern is described in detail by the team at Architecting Bytes, who built a production implementation. Their writeup covers the full relay architecture: activity publishes to Redis, a FastAPI or similar service subscribes and streams to the browser.
A minimal Temporal activity publishing to Redis looks like this:
from temporalio import activity
import redis.asyncio as redis
@activity.defn
async def stream_token_to_redis(channel: str, token: str) -> None:
# Fire-and-forget: no delivery guarantee if no subscriber is connected
r = redis.from_url("redis://localhost")
await r.publish(channel, token)For workflows where every client stays connected throughout, this Redis-to-SSE relay pattern delivers tokens reliably without any additional work. With generation and delivery decoupled, the relay can be scaled independently. But that reliability only holds while every client stays connected. The moment one doesn't, the same fire-and-forget design becomes a structural liability.
Why fire-and-forget is a structural constraint
The Architecting Bytes developer who shipped this Redis-to-SSE relay pattern in production was direct: "This is NOT a durable delivery format, it is purely fire-n-forget."
Redis pub/sub has no storage layer by design: it is a realtime broadcast primitive with no consumer groups, acknowledgment mechanism, or message buffer. Messages that arrive when no subscriber is connected are discarded immediately.
Failure modes in production
Redis pub/sub's lack of a storage layer plays out as three recurring failure patterns once it's running in production:
Client disconnects mid-generation. When a mobile network drops, a corporate proxy times out a long-lived SSE connection, or a tab closes and reopens, the Redis channel carries on receiving events from the Temporal activity with no subscriber. Every event during the disconnect is discarded. When the client reconnects, it picks up from the current moment. Everything that happened while they were gone is permanently lost.
Multiple subscribers with divergent state. A second tab opened during an active generation starts receiving from that moment. Events that arrived before it connected are permanently missing. Two tabs watching the same workflow end up with different, incomplete views. See [Delivering Temporal workflow output to multiple devices](https://ably.com/temporal/temporal-multi-device-workflow) for how a dedicated delivery layer keeps every device in sync instead.
Relay process restarts. The relay service between Redis and the SSE endpoint is infrastructure that you own. When it restarts for a deploy, a crash, or a scaling event, all connected SSE clients are dropped. Clients that reconnect to the new relay instance start from the current event position, with no access to in-flight connections or event history.
When Redis pub/sub is enough
For short workflows where users stay on a single device for the full run, Redis pub/sub delivers messages without issue. The fire-and-forget model is fine when a network drop or a second device is not a realistic concern. Internal tooling, demo environments, and developer-facing dashboards often fit this profile.
The limit is that these conditions rarely hold in production-facing applications. Mobile networks drop, users open second tabs, and long-running agents often complete while users have moved to a different page. The longer the workflow runs, the more likely it is to disconnect.
Alternatives to Redis pub/sub for reconnect and multi-device delivery
Outside single-device, always-connected use cases, closing the reconnect and multi-device gap means reaching for one of three approaches.
Redis Streams
Redis Streams is a different primitive from Redis pub/sub, and it does close the storage gap. Each client can read from any position in the stream, including one it disconnected from, so reconnect replay and multi-device fan-out are both technically possible.
But getting reconnect replay and multi-device fan-out working means building that client-tracking logic yourself. Each browser tab or device needs its own tracked read position, wired through the relay end to end.
Adopting Streams changes the relay more broadly too: it now polls or block-reads the stream instead of receiving pushed events. Streams also has no built-in retention policy or offline-notification primitive, so the team owns trimming the stream and building any notify-later logic separately.
Temporal Workflow Streams
Workflow Streams launched as a Python-only contrib module, with TypeScript reaching Public Preview on 17 June 2026 and Go and Java still on the roadmap. It improves on Redis pub/sub for a single connected device. Subscribers track their own offset client-side, so a reconnecting client can resume from where it left off.
But a second device joining mid-run has no offset to start from. It receives events from the moment it connects and has no way to retrieve what came before. Two devices watching the same workflow see different, incomplete views.
Additionally, roundtrip latency is 100ms, which makes Workflow Streams better suited to step-level progress updates than token-by-token streaming.
Building a custom delivery layer to use alongside Temporal
Redis Streams and Workflow Streams each solve part of the reconnect and multi-device problem, but both leave real engineering work for the team. A custom delivery layer takes that leftover work and builds it as its own dedicated service. That means owning:
Per-client cursor tracking that persists across reconnects, not just in-memory state that resets when a connection drops
A buffer with its own retention policy, so events survive a disconnect without growing the store unbounded
Multi-device fan-out logic that publishes each event to every connected device for a session, not just the one that happens to be listening
Presence tracking, so the service knows which devices are connected and which need an offline notification instead
A relay that survives its own restarts and deploys without dropping in-flight connections or losing position
But that delivery layer is what makes the difference between a Temporal backend job completing and its result actually reaching a browser reliably.
Building one yourself is one way to close that gap. Ably AI Transport is the other: the same cursor tracking and buffering, multi-device fan-out, and presence, already built and shipped as a documented Temporal integration.
Temporal and AI Transport: solving reconnect and multi-device delivery
Ably AI Transport is a documented Temporal integration that ships the delivery layer pattern already built. Ably publishes each Temporal activity's output as a Step: AI Transport's basic unit of published output, keyed to the activity's own Temporal activity ID. When an activity retries, Ably publishes the new output under the same step ID, superseding the failed attempt instead of appending a duplicate.
Cancel signals route through the Ably channel directly. See the Temporal integration guide for the full API.
This replaces the Redis-to-SSE relay with durable, resumable streaming. Visit the Ably AI Transport overview, read the documentation, or sign up free to start building.
Frequently asked questions
Why can't I just use Redis Streams' consumer groups for multi-device fan-out?
Because consumer groups are built for the opposite problem: splitting messages across workers so each message goes to exactly one consumer. They don't broadcast it to every device watching. Multi-device fan-out needs each device to be its own independent reader, tracking its own cursor via plain XREAD, not a shared consumer group.
What does it cost to move off Redis pub/sub once you've outgrown it?
Migrating means either building custom per-client cursor tracking on top of Redis Streams, plumbing that Redis pub/sub never needed. Or it means adopting a delivery layer that already buffers events, replays them on reconnect, and fans them out to every connected device. Teams that wait until a production incident forces the decision typically end up doing both. They patch the existing relay under pressure, then replace it properly once the fire drill is over.
Does Ably AI Transport replace Redis entirely, or run alongside it?
It replaces the Redis-to-SSE relay pattern entirely. There's no need to keep Redis pub/sub or Streams running once the delivery layer is in place. The Temporal side is unchanged; only the piece between Temporal and the browser moves.
Recommended Articles
Delivering Temporal workflow output to multiple devices
Polling and Redis pub/sub both fail the same way with Temporal: the second device joins broken. Here's what actually solves it.
Reaching browsers reliably with Temporal and durable sessions
Temporal keeps backend workflows crash-proof, but getting results to a browser reliably is a separate job. Here's the durable session layer that closes it.
Managing conversation history in a Temporal AI agent chat
A Temporal AI agent's conversation transcript can split across two workflow executions. Here's why, and how to keep transcript storage separate instead.