Relay
Desk
A WhatsApp assistant for a dance studio. It reads what students and coaches talk about and gets each coach ready for their next class.
It runs as a few small services around one WhatsApp connection. The studio's number stays safe while new features keep getting added.
- ~180students
- ~6coaches
- 33safety rules, each tested
- 5separate services
- Two students mentioned a sore ankle
- One is back after three weeks away
- Group asked to run the new combo again
02The project
The problem
Pretty much everything at the studio happens in WhatsApp. Coaches and students talk in program groups and direct messages. Before a class, a coach had to read through all of it to see what’s going on with their students. That takes time, and things get missed.
The studio wanted to streamline that and do more with WhatsApp in general, since that’s where they actually connect with their clients. Whatever we built had to live inside WhatsApp, since a separate app coaches had to remember to open just wasn’t going to work.

Why WhatsApp
We didn’t really have a choice here. The community already lives in WhatsApp, so if this lived anywhere else it would’ve been dead on arrival.
The official WhatsApp Business API was the obvious first option, but it could only post in groups of up to eight people. The studio’s classes are bigger than that, so that ruled it out. Instead we use Baileys, an open-source library that connects the way WhatsApp Web does. The studio keeps its own number, and it keeps working normally on the phone.
Baileys is unofficial, though, and numbers that send like a bot get banned. Losing the number would mean losing the studio’s main channel. A lot of the design is about sending the way a careful person would.
- A new app
- Client portal
- SMS
How it works now
Ten minutes before a class, Claude puts together a summary of what the coach’s students have been saying in the program group and in direct messages. Staff get a few minutes to edit or hold it. Then it goes to the coach, five minutes before class starts.
After class the coach gets a few questions, and their answers turn into a draft post for the group. They can reply YES to send it, NO to drop it, or CHANGE with notes for a new version. All of it runs on its own, without anyone needing to have the app open.
It saves a coach maybe 10 to 15 minutes a class. What I like more is the structure. Every class gets the same before and after, and it kind of turns into a habit.
T-10
Summary is built
Claude reads the coach's groups and direct messages since the last class and writes a short summary.
Who can step in: Nobody needs to. It runs on its own.
03Architecture
Where it started
It started as a way to send WhatsApp messages from Claude Desktop, and that ran into WhatsApp’s rules pretty quickly. A number can only have one live connection per linked device, and a second one knocks the first offline. Every new Claude session would have opened another connection and kicked the last one off. Incoming messages would also only be captured while someone had Claude open.
So one small background process, a daemon, holds the connection full time, and everything else just asks it to send. No message gets missed, even at 2am, and nothing else in the system can knock the number offline by accident.
Keeping the daemon small
The connection is the one part I couldn’t let break, so the daemon does almost nothing else. It saves messages, checks who’s allowed to be messaged and sends at a safe pace. Features like class prep live somewhere else. We can ship them without touching the piece that keeps the number online.
Whether it came from a staff member, a timer or Claude, every message goes through the same checks before it leaves. Group sends are spread over a minute with random gaps, so a class announcement doesn’t arrive like a burst from a bot.
The daemon can also run against a fake WhatsApp, so most changes could be tested without a phone in hand.

Two SQLite databases
Everything’s in SQLite, split into two small database files with one owner each. The daemon owns the message log, and the app owns everything else. Staff can read conversations while new messages are still coming in, and backups are just a file copied every night. Moving to the cloud was mostly copying files too.
The daily send limit is counted from the saved messages, so a restart doesn’t reset it. Messages typed on the phone count toward it too. The daemon also keeps its own copy of who’s allowed to be messaged. Even with the rest of the app down, it won’t message someone it shouldn’t.
The layout
Each outside service, like WhatsApp or the CRM, plugs in through its own small piece in its own process. A problem in one doesn’t take the others down. If the CRM sync fails, messaging keeps working. Each piece also holds only its own password, and the Claude key sits in a process that can’t send anything.
I wanted it modular from the start because I knew new needs would keep turning up, and they did. Testing got easier too, since each piece can be swapped for a fake.
Hover a piece to see what it owns and what it can’t touch.
The libraries, and why
I kept the list short, and each one is there for something the studio actually notices.
- Baileys lets the studio keep its own number and phone. It’s pinned to a version we proved on a real phone, because newer ones failed to connect.
- SQLite, built into Node, means nothing extra to run or pay for, and backups are just files.
- The Anthropic SDK with structured outputs, so Claude answers in a fixed shape the app can check. A bad answer gets caught before it reaches anyone.
- React and Vite for the staff app, which works on a phone and updates live as messages come in.
- Docker and Caddy to run the whole thing on one small server with HTTPS.
Following one message
A student posts in their program's group.
The daemon saves it to the message log right away, so nothing is missed even when no one is looking.
Before class, the API reads the coach's messages from the log and each one is screened for anything trying to trick the AI. Flagged ones are left out of the summary.
Names and numbers are removed, then Claude writes the summary.
The summary is checked for links, numbers and contact details. If anything fails, it isn't sent.
The coach's copy goes out on its own. The group version waits for a person to approve it.

Switching to the CRM
About halfway through, we realized we could sync the studio’s CRM instead of keeping our own roster. Signups already go through it. Now when someone joins a program, they show up in the app on their own and nobody retypes anything. The app still owns the conversations, plus the few things the CRM doesn’t know, like each coach’s WhatsApp number.
The sync runs on its own and can only read from the CRM, so it can’t change the studio’s records. It copies what it reads into the app’s SQLite database, where the rest of the app reads it. If the CRM is slow or down, the app keeps working from the last good copy.
New students are added automatically, but nobody can message them until a person approves the batch. If someone signs up with fake details, they don’t get messaged. A sudden jump in coaches is held until someone confirms it.

One box instead of two
The first plan kept the WhatsApp connection on a computer at home and ran everything else in the cloud. I dropped that because I wanted it running 24/7 without relying on my home machine. If I had a dedicated machine at home I’d have considered it, but for what we’re doing an EC2 instance made more sense. Now it all runs on one small server on AWS. There’s one place to update and back up, and nothing at home has to stay switched on.
The server itself is locked down with no remote login. Each service only gets the password it needs.
04Security
Why it mattered
We’re talking to the studio’s clients directly. Their numbers and emails are in there, and we’re saving their messages. On top of that, the WhatsApp library isn’t an official one. If we send too much or act like spam, the number can get banned.
So the rules are written down as numbered invariants, 33 of them, and each one has a test that fails if it breaks. Most are checked at the very last step before a message goes out, so they apply no matter where the message came from.
The rules
Tap a rule to see where it’s enforced.
05Working with Claude
Where I stepped in
My first ask was basically, what would the architecture be for this problem? What runs today is pretty close to what came back. I mostly had to step in on business details Claude just didn’t have the context for, like when a summary should actually land. Security was the other one, where it was sometimes too strict and sometimes not strict enough.
Then there was anything that needed a real phone. Claude can’t reach WhatsApp from where it runs, so I checked every pairing and live send myself.
Examples
Tap a card to see what changed.
06Process
How we worked
The first 20% or so was the scaffold, getting the processes and boundaries in place. After that we went feature by feature, and I’d give the go-ahead before each next step.
CLAUDE.md was the main thing. It had the architecture, the commands and the invariants, and every session started from it. Each area also got its own plan with dated decisions. I could clear sessions a lot and hand off through those files, without relying on memory.
Anything risky ran on a copy of the live data first, and every feature was checked live before it shipped. Later we broke rules on purpose to see whether the tests noticed. The ones that didn’t got fixed.
My thoughts
Relay Desk started as a way to send WhatsApp messages from Claude Desktop and grew into a set of small services running on AWS. Looking back, a few things stuck with me, some about how I work and some more personal.
Professionally
AI can help with every part of a build. On this one it helped most in the middle, with technical hurdles like getting everything running on AWS. I didn’t know a lot of those mechanics going in. As Claude raised them, I had it help me research what each one meant for the studio, and then I made the call. The hardest calls were about security and figuring out which concerns were actually worth the time, and Claude didn’t always get that call right.
Knowing the product is what anchored those calls. Business process is most of what makes software work. I knew how the team would use it and where it was likely to cause friction. I also knew the early decisions weren’t firm, so I kept the architecture modular. When the requirements changed, new features stayed cheap to add. That’s how I’ve built and directed products before AI, and it carried over almost exactly. Claude gets much further when I bring in what it can’t know about the business and the people using it.
Not long ago, a build like this would have cost a small business a lot of money. The studio’s CRM has a similar feature, but it falls a little short of what they need. With Claude I was able to build something that does more than their full-service CRM, in a reasonable amount of time. I think that shows where products and SaaS are heading.
Personally
What surprised me was how much work it is to build on someone else’s platform and follow their rules. Without WhatsApp this would have been a pretty straightforward build. Most of the complexity came from working within its rules, and that’s also where most of the creative problem solving came from.
I enjoyed learning the mechanics and a lot of new libraries along the way. I also spent time studying how other people prompt AI for visuals and how they build their skills. That’s a big part of why this page looks the way it does. What I’m proudest of is that it’s a full A-to-Z system, built mostly with Claude, and the studio actually uses it every day.
Going into the next project, I’d start with the product again. Claude can help with the rest, and it does a lot better when I know what we’re building and who it’s for.