The bot that lied to cricket fans with a straight face: Temporal x San Francisco Unicorns

AUTHORS
Shubham Londhe
PUBLISHED
Aug 04, 2026
DURATION
14 MIN
  • AI/ML
  • Debugging
  • Durable Execution

Shubham Londhe, Senior Developer Advocate, Temporal

Three in the morning, and my bot was lying to five hundred cricket fans with a straight face. That’s the night this post is about. But it starts months earlier, with an assignment that had little to no spec.

As you may have heard, Temporal now sponsors the San Francisco Unicorns, a Major League Cricket team in the Bay Area. Andrew Baker, who runs Developer Relations for us, handed me an assignment: build something for the Unicorns’ fans. Not a demo, but something they would actually use.

It turned into a WhatsApp bot for the Sparkle Army, the Unicorns’ supporters’ group. You text it like you’d text the friend in your group chat who somehow knows every stat. Gemini does the talking. Temporal does the part I lose sleep over: the reminders, the live-score polling, and everything that has to survive a Worker dying in the middle of a three-hour match.

It mostly did. One night, it didn’t.

WhatsApp chat showing the bot answering a fan’s question about a match result

A real exchange: a fan asks for the result, and the bot answers like a friend, grounded in real data and sourced.

How it started#

I am a cricket fan. I grew up watching India play, which is where the Ashwin fandom comes from, nothing more dramatic than that. I have never lived in the Bay Area, but I visited San Francisco once, liked it a lot, and had already heard of the Unicorns before any of this was my job. So when the assignment landed, to build something for the Unicorns’ fans, I was not exactly a neutral party. It did not feel like work.

Andrew’s brief was deliberately thin to give me room to create: something fans would actually use, still running on a random Tuesday match night when nobody was watching me build it. Having “something” as the only spec was both a gift and a curse. It gave me the chance to fly, but also to fail.

My first instincts were all wrong (and all dashboards). I started with a stats page, a scores widget, and the silly idea that every one of the fans would quietly come to us. But I realized that I couldn’t expect the fans to live in our world, because this simply isn’t where they are. They’re on their phones, mid-argument about the batting order, already typing. So the idea that stuck skipped the destination entirely: something you could just talk to, sitting in the app they already had open.

Fans just ask. Temporal handles the rest.

One more thing shaped everything that followed, though I did not think of it as a constraint at the time. I was building this from India, a little over twelve hours ahead of the Bay Area. A bot for a Bay Area cricket team, written from the other side of the planet, where every live match would land in the middle of my night. I would come to feel that gap more than I expected.

What the fans wanted, and why WhatsApp#

The Sparkle Army is the Unicorns’ official supporters’ group, and they were my audience. So I started from a more natural place and took inspiration from what the fans already ask:

  • When is the next match?
  • What is the score right now?
  • Who’s playing?
  • What is the latest news?
  • Can I predict the result and win some points?
  • Where do I buy tickets and merchandise?

All the ordinary fan questions. Getting a language model to answer those reliably took around eighteen grounded tools and live web search underneath, plus a fair bit of plumbing to keep the model honest. A fan sees none of that because they shouldn’t have to. To them, it is just a chat that happens to know its cricket.

Honestly, I went with WhatsApp for a boring reason: it is where this fan base already lives. Fans already have it, and everyone already knows how it works. Telegram runs off the same core, so I wasn’t betting everything on one channel, but it wasn’t close. Somewhere around ninety-seven percent of sign-ups came through WhatsApp. The fans had already picked the room. I just showed up in it.

There was one rule I would not bend: every score and every fact had to come from real data, never from the model. The bot can talk like a fan, joking about a batting collapse if the moment calls for it, but it can’t make up a number. That line between having a conversation and stating a fact is where all the trust in the thing lives. It is also the exact line I would watch blur later, on the worst possible morning.

WhatsApp welcome card for the San Francisco Unicorns Fan Bot

How it is built#

Every message, from either channel, runs through one conversation Workflow. WhatsApp and Telegram take the exact same path inside. The only thing that changes is how the answer gets formatted on the way out. Underneath, four Temporal Workflows do the work, and it’s worth naming them because the names are basically the design.

conversationTurnWorkflow runs on every single message. It checks the budget, loads the last bit of context, lets Gemini decide which tools to call, runs them, and sends the reply. This is the talking part, and it is the only one a fan sets off directly.

matchReminderWorkflow is a reminder that actually keeps its word. It goes to sleep until half an hour before play, then wakes up and pings the fan. It runs on a durable sleep, so a restart during the wait doesn’t quietly forget you the way a cron job or an in-memory timer would.

matchScoreSubscriptionWorkflow is the live-score follower. Once a fan subscribes, it polls every sixty seconds for the whole length of a match, and it uses Continue-as-New as it goes so its Event History never grows without bound. This is the one that has to survive a three-hour game without dropping a ball.

postMatchSummaryWorkflow wakes up once, when the match ends, and sends the final result to everyone who subscribed to that game.

I could have built all of this with a few timers, a queue, and some optimism. The deciding factor was that if a Worker dies in the middle of a live match (and, trust me, Workers do die), every one of those Workflows has to carry on exactly where it left off. With Temporal, I got that for free, without writing a line of recovery code of my own. That decision looked boring on the day I made it, but stopped looking boring the morning this whole post is building toward.

I will be honest about how these got built, too. I leaned heavily on Temporal’s own developer tooling and AI assistance to get the Workflow patterns right. A large part of why four durable Workflows came together in the time I had is that I was not working out every pattern from scratch on my own.

Architecture diagram showing the Superfan Bot’s channels, Temporal Workflows, Activities, services, and data stores

Rough architecture: channels to bot to Temporal Workflows to Activities to data.

WhatsApp chat showing live scores and a daily usage-limit notice

Live scores streaming during a match, with the budget gate at the bottom being honest when a fan hits the daily limit.

The WhatsApp surprises#

Building on WhatsApp means building inside someone else’s rules, and I learned most of them the hard way.

The big one is the 24-hour window. WhatsApp only lets you message someone freely for twenty-four hours after their last message to you. Once that window closes, the only thing that gets through is a pre-approved template. Think about what that does to a bot whose whole job is reminders and live scores. Those are exactly the messages that arrive when the fan is not currently sitting in the chat with you, so this was a huge obstacle for the code. It shaped the whole design. I send a template hint with every outbound, fall back to a proper template once the window has closed, and skip the send cleanly when even that will not land, instead of wasting it.

The rest of the surprises came in a steady drip. Every webhook from Meta arrives signed, so I verify each one with a constant-time comparison before I trust a single byte of it. Meta also delivers at least once, which sounds harmless until you realize a single duplicated message can start two separate conversations for the same fan. I key off the WhatsApp message ID and drop the repeats.

Interactive replies are capped at three buttons, whereas Telegram has no such limit, so the welcome card is built down to the smaller number and looks deliberate on both channels. WhatsApp also wants one fixed, public HTTPS address for its webhook, so I run a tunnel in front of it that survives restarts and redeploys without me re-registering with Meta every single time.

None of these were hard on their own. Stacked up, however, they were a running reminder that sending a message stops being simple the second a real platform sits in the middle of it.

The sleepless nights#

Here’s the night I promised you, except for me it was not night at all. As I mentioned before, I live in India, a little over twelve hours ahead of the Bay Area, so the Unicorns play in the small hours of my morning. From the very first match, the bot’s biggest moments were happening while I was fast asleep. There is something strange about building a thing for a live audience whose prime time is your three in the morning.

So on the first match day, I was not the one watching it. Ian Douglas, a colleague of mine, was keeping an eye on the bot from the U.S. side while I was out cold. I woke up to a text from him. The scores looked stale. The bot was calmly telling fans about a match that had already moved on without it.

I did what you do when you wake up to a message like that. I pulled up the live match and the bot side by side, still half asleep, and watched. On the actual scorecard, an SFU batter had just launched a six. My bot, cheerful as ever, was reporting that a wicket had gone down.

It was not an error and, even more frustratingly, there was no crash, no stack trace, no alert, nothing red on a single dashboard. Just a confident, friendly, and completely wrong answer, delivered in the same tone it used every time it was right. It got under my skin because if Ian hadn’t been awake and watching, it could have carried on like that for hours, spouting off lies.

So I started combing through the logs first, then API hits, then the Temporal Workflow runs, one after another, looking for the place where the numbers stopped agreeing (all while still in my pajamas). I kept pulling threads until the live match, the API responses, and what the bot was actually saying all lined up into one story, and the story was embarrassing.

The problem was all mine. The code I wrote to stay under the old free-tier limit didn’t know the limit had gone up. It hit the old limit, decided we were out of budget, and quietly stopped calling for fresh scores, while the real key sat there with thousands of calls to spare.

The part that really got me was that once that guard tripped, it could not recover on its own. The call that would have reset its counter was the very call it was now refusing to make. It had locked the door and thrown the key inside. So it just sat there, serving a scoreline frozen a few minutes in the past, sounding as sure of itself as ever.

The fix itself was almost insulting: one wrong number, corrected after a morning like that.

The other half of it, honestly, isn’t the war story you might be expecting. I went looking for some dramatic moment where a Worker fell over mid-match and Temporal heroically caught it. I don’t have it. What I have is quieter and more convincing. On a single match day, more than five hundred Workflows ran: reminders, live-score subscriptions, conversations, and post-match summaries, all at once, and every one finished. Not one fan messaged about a missed reminder, dead scores, or a duplicate ping. All of it was happening on the other side of the world while I was asleep. The only thing that broke that night was my own quota guard, not Temporal’s.

Grafana panel showing the bot’s backend activity

A Grafana panel showing the backend function of the bot.

Launch and the response#

The Unicorns side is what made this possible. Lisa White, an experienced Partnerships Account Manager on their side, got the bot in front of actual fans and kept me honest about what they’d want, which was different from what I’d assumed sitting alone with my code. She also quietly absorbed the twelve-hour gap between us, taking calls at odd hours on her end and waiting patiently while I caught up on mine (thanks, Lisa!).

We soft-launched to a small group of testers first, watched how they used it, then opened it up properly. The pattern showed up almost immediately. Sign-ups climbed on match days, growing when the team played and going quiet when they didn’t, exactly what you want from something built for fans. The best part is that they came back every time the game was on. Opt-outs stayed near zero, and almost all of the activity lived on WhatsApp, the same place the sign-ups came from.

The part I did not see coming was what fans actually wanted from it. I had built the thing around scores, and scores were only the start. Soon they asked about the players they came to watch, particularly captain Matt Short and Ashwin (and the Ashwin fan in me enjoyed those messages more than I should admit). They asked the bot to remind them before a match started and to keep them posted on the score once it had. They asked about Family Day. And more than anything, they argued about predictions, with the bot and with each other, because the prediction game gave them a reason to keep coming back between matches instead of only during them. Three points for calling the winner, two for the margin, five for player of the match, and a leaderboard to fight over. Somewhere in there, the bot stopped being a scoreboard and started being a place to be a fan.

Where it lands#

Let’s travel back to that morning for a second: the six on the scorecard and the bot calmly insisting on a wicket. That is the moment this whole post turns on, because it sits right on the line between two things that look identical until the day one of them breaks. Code that runs, and code that keeps its promise.

Durable Execution did not make my code correct. I wrote that quota bug with my own hands, and no framework on earth was going to save me from myself. What it did was take care of everything around the bug. Reminders still fire even if the Worker was down the moment they were set, and a subscription picks back up after a restart instead of dying in silence. Retries don’t double-bill a conversation, either. And on the worst morning, it gave me a full, replayable record of exactly what the system did, so a silent failure had nowhere left to hide.

The wider thing I took from it is that this isn’t only about cricket. Anything that has to wait, poll, retry, or fan out over time is a durability problem wearing a costume. A WhatsApp bot and a payment flow look nothing alike on the surface, but they’re the same shape underneath. Cricket just made it fun to build, and impossible to fake, because the fans would know instantly if I got it wrong.

Shubham Londhe wearing a San Francisco Unicorns jersey

It all worked out in the end. Go Unicorns!

Temporal Cloud

Ready to see for yourself?

Sign up for Temporal Cloud today and get $1,000 in free credits.

Build invincible applications

It sounds like magic, we promise it's not.