I’d been at Temporal for less than two weeks when my first major project landed: help architect and build a conference-level registration system for Replay on Temporal.
Not just a registration form. A full ecommerce system with email, coupons, T-shirts, swag items, workshops, and a lot of PII to encrypt in the database. Time to roll up my sleeves, get into the thick of it, and absorb knowledge from folks with years of Temporal experience. What might have been a tangle of code patching together a bunch of PaaS/SaaS systems became a handful of durable, observable processes, without losing any sleep.
I’ve been an API architect, developer, and consumer for most of my engineering career, and I love wiring things together. But the don’t-charge-twice paranoia that every ecommerce developer worries about became the platform’s problem instead of mine. Every order became a durable process, and I spent more time learning Temporal than building dashboards to understand whether the system was working.
Replay 2026#
Temporal’s annual conference, Replay, had its fifth iteration last month. We wanted to customize quite a bit about our conference experience, but third-party software and PaaS/SaaS systems were getting in the way of the conference we wanted to run. So we took on the ambitious project of building our own systems, on top of Temporal, to customize every aspect for attendees.
As one of the key developers on the project, I wanted to share what we built, how we used Temporal along the way, and how we could do even more with what we announced at Replay 2026.
The plan was solid, but there were a lot of moving pieces: payments, attendee records, ticket email, registration forms, sensitive data encryption, check-in, badges, T-shirt eligibility, workshops, and reminder notifications.
The web app reaches Temporal Cloud, where Workflow code orchestrates Activities that call the payment processor, the email API, the database, and analytics/CRM.
Nobody cheers about the checkout that worked#
When a user registration flow works, it is invisible to the attendee. Nobody writes a glowing review of the checkout flow that simply did its job. But charge a card twice, lose a confirmation email, have a registration go missing, or silently reject a coupon at 11 p.m. on the night early-bird pricing ends, and you will hear about it. Usually loudly, publicly, and with screenshots.
So the unglamorous path is the one you have to get right the first time. Not the happy path, but the path where the payment processor times out, the email provider decides to rate-limit you, and someone clicks the “pay now” button over and over because they were not sure the first one took. That path has to be correct, and stay correct for the months between “I bought my ticket four months ago” and “I’m at the registration desk checking in.”
A strange shape for software, but exactly the shape Temporal is for.
A ticket is a tiny, months-long story#
When you walk through what actually happened when you bought a ticket, you’ll see what I mean. Maybe you typed in a coupon code (I made a lot of those for attendees), which had to be checked not just for existence, but for whether it could still be used. We charged your card exactly once. Ever. We saved the order and reserved a ticket for every seat. We emailed you a receipt, and if you bought multiple tickets, we emailed each attendee a registration link.
Then we waited.
Within a few days, ideally, each attendee filled out their registration. In a few cases, this took months and several reminder emails. Some people did not do it until they were in line checking in at the conference. But loading that registration form woke things back up: validate the form, generate a QR code for check-in day, and send another email that everything is finished. Maybe someone wants to transfer their ticket to a coworker. Somebody else cancels and wants their money back. Several people change their minds about which workshop they want to attend, or demand for one workshop surges so much that we have to expand seating three times at the conference center.
And then everything waited again, together, until the doors opened at Moscone.
None of that is a single request/response flow anymore. It is a whole event-driven process with a long, patient life of its own, stitched from a dozen steps that each poke at some external system that can fail.
If you’ve used Temporal, you already feel it. We were made for this.
A quick word on the pieces#
Three terms carry the rest of this post, so let me define them for everyone:
A Workflow is durable code. It is written as ordinary application code, but Temporal records what happened in Event History, so if the process crashes, or the machine running it falls into a fishtank of water (IYKYK), it resumes exactly where it left off. Even if where it left off is asleep, waiting for something that will not happen for four more months.
An Activity is the part that touches the outside world: charging a card, checking a coupon code, sending an email, checking which swag an attendee can pick up, or writing a row in a database. Temporal retries Activities when they fail according to the Retry Policy you set, so developers like me do not have to carry all of that retry logic ourselves.
A Signal is a nudge you send into a running Workflow from outside, such as “payment came through.” A Query reads state back out, such as “what is happening with my order?”
Here’s how we used these as the architecture of our growing stack to power Replay conferences in the future.
The cast of thousands (of Workflows)#
One Order Workflow spawns one Ticket Workflow per ticket, and reports into a long-running stats Workflow.
We ended up with a small ensemble:
- An Order Workflow, one per purchase, which owns the money and the overall arc.
- A Ticket Workflow, one per ticket, each living its own independent life right up to the event.
- A long-running stats Workflow that the live “tickets remaining” counter quietly reads from.
The browser never talks to Temporal directly. The web app’s API starts Temporal Workflows and sends Signals to them; a separate Worker runs the actual Workflow code.
An order is a process, not just a row in a database#
The order’s state machine: wait for payment, create tickets, sleep until the event.
One clarifying decision we made was to stop treating an order as a row that various jobs scribble on, and start treating it as a process that runs.
When you checked out, we started an Order Workflow. It validated your coupon if you had one, and handed off the payment to a payment processor. Once payment landed, the order saved itself and its tickets in one transaction, written so that a retry could not create a duplicate. That action spawned the Ticket Workflows and emailed the receipt. And then it did my favorite small thing in the whole system: it waited.
Compare that to how we would have built it without Temporal: an orders table, a cron job sweeping for stale or finished rows, a status column I would pray nothing else was writing to, and a separate Worker to update the order after the event, then create each ticket with more cron jobs. We simplified all of that. The waiting is in the Workflow code now.
Every ticket gets its own brain#
One architecture pattern we recommend: every ticket gets its own Workflow, spawned as a child of the Order Workflow.
I’ll be honest, that felt like a lot of Workflows when we first drew it up. We figured on thousands of orders, but also thousands of tickets, each with its own additional Workflow? It felt redundant, but turned out to be the decision that kept everything else working smoothly. Each Ticket Workflow ran its own little loop, sleeping until the event and waking for the Signals that mattered to it:
while (!eventReached) {
await condition(() => registered || transferRequested || cancelRequested);
if (registered) await handleRegistration(); // validate, QR, email
else if (transferRequested) await handleTransfer(); // hand off to a new owner
else if (cancelRequested) await handleCancellation(); // human handled a refund flow
}
Because each ticket is its own little world, one attendee can register and manage their ticket without disturbing anyone else. A flaky email provider on one step does not hold the other steps hostage. And no single Workflow’s Event History grows out of control just because somebody bought a ticket in February and did not register until the day the conference started.
Activities are where the world gets to misbehave#
Everything that can actually go wrong, including the payment processor, the email provider, the database, and the analytics and CRM systems we sync to, was an Activity. And the reason that matters is this block of configuration:
const { processPayment, sendConfirmationEmail /* ...and friends */ } =
proxyActivities({
startToCloseTimeout: '5 minutes',
retry: { initialInterval: '1s', backoffCoefficient: 2, maximumAttempts: 3 },
});
Each external processor had a set window to complete its piece of work, with configurable retry and backoff options directly in the code. We did not need to write custom try/catch loops around every call, or stand up a queue to make retries happen. Designing the code with Temporal hands those responsibilities to the platform, which maintains state and manages queues for you.
What if the payment processor goes down? Charging someone multiple times because of poor code is unforgivable. I built the billing system so each order had an idempotency key. Retrying the Activity after the charge has already gone through means the processor hands back the original result instead of reaching into the customer’s wallet a second or third time.
The side effect I loved: every step lands in Temporal’s Event History as part of the order’s story. When a support email asked where a confirmation had gone, I did not go spelunking across five services’ logs. I opened the Workflow and read the whole thing top to bottom, like a transcript. Yep, there’s the email Activity, and its response was that the email was delivered. I also had scripts to rerun the receipt-sending Activity if someone needed another copy for an expense report. And on occasion, I helped a business figure out why our Replay emails were going missing. We sometimes got filtered into their Gmail “Promotions” view.
Everything Temporal records for one order on a single timeline, which is also exactly what you get to read back when something looks off.
Coupons were out to get me#
Coupons were a tricky part of the project, and one thing I’ve already started automating for our next Replay event.
The codes lived in our payment processor, managed by a small pile of homegrown scripts to add or manage coupon code usage. But the processor’s built-in usage cap was not a good indicator of actual usage, so we stashed our own capacity number alongside each code in coupon metadata. When a user tried to apply a code, the Worker checked it against a live count of how many tickets had actually sold with it.
We could update any coupon code at any time, and usage would update in real time because Activities checked live count data. No code deployment. No downtime.
Changing the tires while the car is moving#
Sometimes the process changes after the architecture is already in motion: how we tracked registration data, which fields we collected, or how much private or encrypted data we needed to handle. Since these Order and Ticket Workflows were already running, we had to choose between versioning our Workflows and Activities or patching them for new behavior.
Temporal has a tidy answer for this. You gate the new behavior behind a version check:
if (patched('activity-name')) {
// new: handle whatever needed to change
} else {
// old: the behavior the in-flight workflows already committed to
}
Workflows already in motion keep taking the old path when their Event Histories replay, so they never trip over the changed code. New Workflows take the new path. You can deploy patched code for new Workflows, while your existing Workflows keep humming along on the behavior they started with, never the wiser that something has changed.
Counting tickets without melting the database#
The “tickets remaining” number on the site did not run a fresh count against the database on every page load. It read from one long-running stats Workflow and asked it for the latest tally through a Query. A Workflow, treated as living, queryable state, rather than a job that runs once and disappears.
Not everything needed a Workflow#
One learning point in the building of this, for me, was implementing a Resolver pattern. I set up a single Workflow with lots of single-use Activities: one-off database queries like looking up a ticket by UUID, or getting an order’s status. These worked great, but you can quickly hit Event History limits if you call too many Activities, and then you need to Continue-as-New to refresh the history in a new Workflow Execution.
One of our big announcements at Replay 2026 was Standalone Activities. These are incredibly useful for one-off Activities where you do not need to define a whole Workflow. This will be our number-one change going forward. It can also reduce billable actions if you use Temporal Cloud.
What I’d tell you over coffee#
Was it worth the effort? Absolutely. Not just for my own learning of how Temporal works. I’ve had many jobs where teams evaluated the whole build-versus-buy trade-off that comes with using a third-party vendor that cannot accommodate the custom changes you want. Do you go with 60% of what you want and live with it, or spend the extra effort building and maintaining it yourself?
Selling conference tickets is not a hard distributed-systems problem in the way people usually mean it, and selling a few thousand tickets is nowhere near the scale of some of the companies using Temporal. But a solid ecommerce site is a hundred small things that each have to be right, that stretch across months, and that can fail in boring, expensive, public ways. That middle ground, something too long-lived for a request handler and too important to leave to a cron job and a prayer, is precisely where Temporal wins the day.
Where we go from here#
I’m already hearing whispers of Replay 2027, and now that we’ve announced support for Standalone Activities and Serverless Workers, there’s so much more we can build to handle bursts of traffic and simplify what we’ve built. I’m excited to continue working on our Replay registration systems to see where this goes and how we’ll manage our data for future events. I love building abstractions, so wiring in a new email provider or payment processor will be even easier.
Huge kudos to my teammates who helped prove this initiative, and to our leadership team for the vision to carry it forward.
Building large-scale or multi-piece applications on Temporal sometimes means rearchitecting things, and it might seem overdone to shift into distributed workload thinking. The payoff is better than you think when you consider the future time saved and how easily you can start to tear away custom retry/replay logic and handlers. Come hang out in our Community if you have questions.