Tillum Labs

RadioWash

A SaaS product that makes clean copies of Apple Music playlists — the same songs, with radio edits swapped in where they exist. I built the whole thing — architecture, backend, frontend, payments, infrastructure — and it runs in production today at radiowash.com.

The problem

Explicit music is a real constraint in a lot of places: gyms, retail floors, school buses, family cars, church youth groups. Apple Music has clean versions of most popular songs — radio edits, released as separate tracks — but no way to apply them to a playlist you already have. Your options are rebuilding the playlist by hand, track by track, or giving up and playing something generic.

That's exactly the kind of gap software should close: the information exists, the work is mechanical, and nobody wants to do it manually.

What I built

You sign in, connect your Apple Music account, and pick a playlist. RadioWash reads every track, finds the clean version of each explicit one, and builds a new playlist in your library — your original is never touched. While it works, you watch live progress, and when it finishes you get a track-by-track report: which songs were already clean, which were swapped, and which have no clean version at all. Those last ones are left out rather than guessed at, so the copy contains only clean material.

Cleaning is free. For subscribers, there's a second layer: Auto-Sync keeps a clean copy in step with its source — add songs to the original playlist and the copy picks up their clean versions overnight, so the version playing in your gym or your car stays current without anyone touching it.

How it's built

Three main pieces:

  • The web app — what you see in the browser. Built with Next.js and React, the same tools behind sites like Netflix and Notion.
  • The backend — a .NET service (Microsoft's framework for server software) that does the actual work, running in containers on Microsoft Azure so it deploys the same way every time.
  • The database — PostgreSQL, hosted on Supabase, holding accounts, jobs, and the per-track record of every playlist ever cleaned.

The shape of the system comes from one constraint: cleaning a playlist is slow. A playlist with a few hundred tracks means a few hundred individual requests to Apple's API — the interface programs use to talk to Apple Music — and Apple limits how fast you're allowed to make them. That can take minutes — far too long to happen while a web page sits loading.

So the work runs through a background job system (Hangfire), which queues each cleaning job, runs it on the server, and retries it if something fails partway. Meanwhile the browser holds an open connection to the server (SignalR) that pushes progress updates as they happen — which is how you get a moving progress bar instead of a frozen screen. If your connection drops, the page reconnects on its own and picks the updates back up.

Subscriptions run through Stripe. The backend carries about 580 automated tests, and the integration tests run against a real PostgreSQL database started fresh in a container for each run — so the tests exercise the same database the product uses, not a stand-in.

Decisions worth explaining

Built to outlive its platform. RadioWash started life on Spotify. Partway through the build, Spotify changed its API rules so that apps in the development tier could serve only five users — which is not a product anyone can ship. That could have been the end of the project. It wasn't, for two reasons. The pivot happened before launch, so no users were stranded. And everything platform-specific already sat behind one shared interface in the code, so rebuilding on Apple Music meant writing a new implementation of that interface — the matching pipeline, the job system, payments, and the UI all carried over. That same seam is why the product can take on another platform later — or Spotify again, if its terms ever change back — without being rebuilt.

A wrong match is worse than no match. A search for a clean version returns candidates — but a candidate might be a cover, a karaoke version, or a different song that shares a title. Swapping in the wrong song silently would be worse than admitting no match exists. So matching runs in strict order. First by ISRC — the recording industry's identifier for one specific recording — which either finds the clean counterpart of exactly that track or finds nothing. Only then does it fall back to text search, filtered by title, artist, and a duration match within three seconds. And because Apple's search can't exclude explicit results the way Spotify's could, every candidate's content rating gets checked before it's accepted. If nothing survives all that, the track is left out — never substituted. That means fewer matches, and I accepted the trade. What makes it workable is transparency: every track's outcome is recorded and shown to the user — swapped, kept, or "no clean version found" — so you always know exactly what's in the playlist you got back.

Living with API speed limits. When you ask Apple's API too fast, it answers with "slow down, wait this long." The obvious move is to wait exactly as long as it says. But a broken or hostile answer could demand an hour, and obediently sleeping that long would tie up the server for one user's playlist. So the wait is obeyed up to a cap of sixty seconds; anything longer fails fast, and the job system reschedules the work for later.

Payment events that can't be lost or doubled. Stripe tells the app about renewals, cancellations, and failed payments through webhooks — automated messages sent to the backend. Two things can go wrong: a message gets lost, and a paying customer loses access they paid for; or a message gets processed twice, and something happens twice that shouldn't. I built for both. Every event carries an ID that gets recorded, so a duplicate is recognized and skipped — each event is processed exactly once. A failed event lands in a retry queue that tries again at increasing intervals, so a temporary outage delays processing instead of dropping it. And there's a 24-hour grace window on expiry, because Stripe's renewal notice can arrive minutes after the official expiry moment — nobody should get cut off inside that gap.

Outcome

RadioWash is live in production at radiowash.com. Cleaning is free with an Apple Music subscription; Auto-Sync is the paid tier at $5/month. It's early — there are no growth numbers worth quoting yet, and I'd rather show none than invent some. What the project demonstrates is the part clients actually need: one person carrying a product from the first architecture decision through payments, deployment, and production operation — and keeping it alive when the platform underneath it moved.