# How we cut over a 45-million-message alerting system with Claude Code

> Source: <https://kznconsulting.com/writing/alerting-system-cutover-with-claude-code>
> Published: 2026-10-06 00:00:00+00:00

We just moved an alerting system that has sent and received 45 million messages onto our main platform. Its last remaining client, a large local government, uses it to send about 17,000 residents reminders by text, email and app notification, things like when to move their car for street cleaning. Along with the move, we launched a new web app for residents and two new native mobile apps, one for iOS and one for Android.

The cutover meant moving every subscription, switching off the old system, pointing web and app traffic at the new one, releasing both apps and turning reminders back on, all in one maintenance window.

We've run a lot of cutovers, and this was the smoothest (and most complex) one we've done. We ran it with Claude Code, from a runbook we had written and rehearsed with it.

## Why we moved

The old system's first commit is from July 2017. It served us well for nine years, including through the pandemic, when it carried test-result messaging for a state health department. But its web front end was built on Ember.js, whose community has thinned out, and it had gotten hard to find developers who wanted to work in it. Meanwhile, most of our work had moved to our newer platform, which was originally built out of this one. This client was the last one on the old stack, so every feature we added had to be built twice, once for each system.

## Every step has a command and an expected output

Everything for the day lived on one page in our internal library. Each of the 61 steps has the exact command to run, what it should print, and what to do if it prints something else, so whoever runs the step can tell whether it passed.

The page also set the session's rules, like stopping whenever a step didn't match.

## The operator made the calls

We named one engineer as the operator for the day. 28 of the 61 steps are marked GO, which covers anything that writes to production, from the deploy to the app store releases. Before each one, Claude said what it was about to run and waited for a go. It ran the read-only checks in between on its own.

Claude had the access to run all of it. We decided where the line sits while we were writing the runbook, so nobody had to make that call in the middle of the morning.

Looking back, we drew that line too wide. Only about six of the 28 needed a go, the ones you can't take back: the deploy, moving live traffic, turning sends on and the app store releases. The rest were rehearsed changes on data no resident could see until sends went live. By late morning the operator was already approving those as one block. Next time they run on their own.

## Everything on one dashboard

Before the day, we built a dashboard: every step as a card, with its status, what it should produce and what actually happened. After each step, Claude wrote the result onto the card and republished it. The operator kept it open on a second screen, read one line per step against what we expected, and when a GO came up, had the facts for the call already on the card.

## When something didn't match

Six things didn't go to plan on the day, and none of them reached a resident. Each one surfaced at a check on the page, the run stopped there, Claude proposed a fix, and the operator made the call. The longest cost about half an hour.

## If the model goes down

Every step is a command a person can paste into a terminal, with the output to check it against, so the operator could have picked up any step by hand. Progress was on the dashboard and committed to the library at the end of each phase, so a person or a new session could pick up at the next step.

## Dry runs still matter, and they're cheaper now

The dry runs are where the real work happened, as on every cutover we've done. Claude made each one cheap. It ran the sequence against a fresh copy of production, reported what didn't match, and we fixed the runbook after every pass, so we rehearsed this one many times.

Each rehearsal turned up something new, and each finding became a line in the runbook.

## The result

The run started at 9:45 and finished at 12:50, well inside the window. About 17,000 residents and 133,000 subscriptions moved over, and both apps were released by 12:15. Reminders were paused for 47 minutes, on a Friday morning when none were due.

Since then the reminders have run on their own. The first two evening runs, Sunday and Monday at 7 PM, went out by text, email and push with nothing stuck.

## What we'd do again

Write the runbook so someone who has never seen the system could run it. Give each step the command, the output you expect, and who decides when they differ. Name one operator, keep their go for the steps you can't take back, and let the rehearsed ones run. Rehearse more than once, since an agent makes that cheap. And keep one page that tells you where things stand at a glance.
