Brewing Coffee: Talking to Customers in Real Time

AniruddhaAniruddha
5 min read

Over the last few posts, we’ve been building the backend of a coffee shop using Temporal, modeling the key steps in fulfilling an order as distributed activities: taking payments, checking supplies, brewing coffee, and serving it. These are all orchestrated by Temporal workflows, ensuring reliability and observability.

But there’s one critical part of this system we haven’t touched yet—the customer’s experience.

How does a customer place an order? How do they know payment requested? That’s the focus of this post: designing a customer-facing interface that will eventually plug into the Temporal backend we’ve built.

We’re building a “counter-service”—a small web application that:

  • Provides a form for customers to place an order.

  • Shows real-time status updates as their order moves through the system.

To keep it lightweight and responsive, I’ve built the interface using Starlette. The UI is rendered using Jinja2 templates, allowing for clean and dynamic HTML generation. On the frontend, some simple CSS makes the interface feel polished without adding heavy dependencies. And bit of JavaScript listens for sending order to our “counter-service” & for real-time updates over SSE.

Customers interact with a minimalistic single-page form where they place their order. After submission, they remain on the same page and receive live updates as the order progresses. For example:

  • When payment is required

  • Once the coffee is ready

How the Flow Will Work

Here’s the envisioned end-to-end flow for a customer order:

  • A customer submits a coffee order through the web form.

  • The web app hits a REST endpoint in background, which will soon be wired to start a Temporal workflow.

  • As each step in the workflow completes (e.g., payment received, coffee brewing), the corresponding activity will send an update to the system.

  • These updates will be pushed to Redis Pub/Sub.

  • The counter-service will subscribe to Redis and forward those updates to the customer’s browser using Server-Sent Events (SSE).

The result? A real-time interface where the customer is automatically notified when payment is required and when their coffee is ready, all without refreshing the page.

Redis Pub/Sub: Decoupling Workflows from the UI

Temporal is excellent at orchestrating workflows, but in our coffee shop system, we want to keep workflows and activities clean and side-effect free—especially when it comes to external concerns like the user interface.

Temporal expects activities to be side-effect-free and retryable—and updating a live UI doesn’t fit that model. Imagine if the payment activity fails and gets retried: without safeguards, the customer might see “Please pay $5”… again… and again… and again. Not a great user experience — unless your goal is to make coffee and a lawsuit.

To know more about redis pub/sub check

SSE: Keeping Customer Informed in Real Time

To get those updates from the server to the browser, I’ve integrated Server-Sent Events (SSE) via sse_starlette.

SSE is a lightweight way to push updates from the server to the browser using nothing more than a long-lived HTTP connection. No WebSocket handshakes, no complex protocols—just HTTP streaming a series of text/event-stream messages. And for our needs, it’s perfect: we don’t need two-way chat, just a one-way tell the customer when payment required & coffee is ready.

What’s Brewing Behind the Counter

Here’s where things currently stand in the build:

✅ The customer-facing interface is functional: A Starlette-based form allows order submission, and the browser listens for real-time status updates via SSE.

✅ Redis Pub/Sub is integrated: Activities (soon) will be able to publish updates to Redis, which the counter-service will relay to the frontend.

🛠️ The docker-compose.yml has been updated: Added Redis service, counter-service pass redis host, port via env variables & open port 9090 to listen web request.

🛠️ Counter-service code has been extended to support Redis subscription and event streaming. It’s ready to plug into Temporal once the activities are connected.

Still in progress:

⏳ Connecting Temporal workflows and activities to emit updates to Redis.

⏳ Handling customer-specific sessions for tracking order status cleanly.

You can explore the current code and structure here:

👉 View on GitHub

Once you’ve checked out the code and spun everything up:

  1. Open http://localhost:9090 in your browser. You’ll see the order page (example screenshot below).

  2. Place an order—there’s no input validation yet, so make sure to provide valid values.

  3. After submission, you’ll see a notification on the same page where updates will appear.

Testing Redis & SSE

To see the backend wiring in action:

  • Connect to Redis at 127.0.0.1:6379.

  • Run the command:

best-coffee-shop> best-coffee-shop
127.0.0.1:6379> pubsub channels

You should see a channel with a name based on the customer you entered.

  • SSE events stream over this channel. The code currently supports these three events: orderReceived, reqPayment & coffeeReady

  • When coffeeReady is published, the client will show a “Your coffee's ready, grab it!” message and the Redis Pub/Sub channel will be closed.

To simulate that event manually, run:

127.0.0.1:6379> publish <channel-name> "coffeeReady"

You’ll see the browser respond in real time, and Redis will stop showing the channel.

Deployment Note: Local vs Docker Networking

Note: The docker-compose.yml file defines environment variables like REDIS_HOST, REDIS_PORT, TEMPORAL_ADDRESS, and WEB_APP_PORT to keep services flexible across environments.

In the counter-service code, these are accessed like so:

import os  
temporal_host = os.getenv("TEMPORAL_ADDRESS", "localhost:7233")

This setup allows the service to work seamlessly both:

  • On your local machine during development (using localhost defaults)

  • Inside the Docker network defined in docker-compose (with environment-specific overrides)

Since counter-service is exposed to the customer (via the browser), this approach ensures it connects correctly to Temporal and Redis regardless of where it’s running.

Last Sip Notes

We’ve laid the groundwork for a real-time customer interface—without reaching into Temporal from the UI or vice versa (because nobody wants their frontend tangled in retries).

The current setup already lets customers:

  • Place an order

  • See live updates via SSE

  • Watch their coffee journey in real time, from “Order Received” to “Coffee Ready” (sadly, we can’t stream the aroma… yet)

There’s still more to come:

  • Plugging activities into Redis updates

  • Full round-trip integration with Temporal workflows

  • And maybe… adding some polish so the form doesn’t let you order a “cold espresso with foam and sprinkles”

In the next post, we’ll hook the remaining pieces together and give our digital barista full workflow powers. Until then, may your services stay up and your coffee stay hot.

0
Subscribe to my newsletter

Read articles from Aniruddha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aniruddha
Aniruddha