Live

Getting a one-person SaaS through the WordPress.org review

AudioLinter checks podcast and spoken-word recordings against the EBU R128 loudness standard and repairs them in one click — inside the WordPress editor. Behind it sit eight services in production and 36 shipped releases. The hardest part was not the audio processing but the official plugin directory review.

Visit the project
Releases shipped
36Source: CHANGELOG.md and wordpress.org, as of 11 Aug 2026 (v1.0.36)
Services in production
8Source: docker-compose.prod.yml, as of 11 Aug 2026
Test files
140Source: Repository, as of 11 Aug 2026
Commits
705Source: git log, 6 Mar 2026 to 10 Aug 2026
Lines of source: backend / plugin / website
17,952 / 12,417 / 14,592Source: Repository excluding dependencies, as of 11 Aug 2026

Publishing podcasts through WordPress means loudness problems surface only when listeners complain: one episode too quiet, the next clipping, seconds of silence in between. The tools that catch this are audio applications, so the file has to leave the publishing tool and come back. That detour was the thing to remove.

A running service listed in the official WordPress.org directory and updated through it. 36 shipped releases, eight services in production, 140 test files. The directory review is a real gate covering security, output escaping and internationalisation — passing it is the project's most defensible quality claim.

The problem was the detour, not the analysis

Loudness checking is a solved problem — tools have existed for years. What was unsolved was the route: publishing an episode through WordPress and then having to download it again, load it into an audio application, check, correct, export and re-upload. With weekly episodes, that is the point where quality control quietly stops happening.

So the product decision was not “build better analysis” but “bring existing analysis to where the file already is.”

Why that became eight services

A WordPress plugin cannot process audio. Shared hosting has neither the compute time nor the libraries, and an HTTP request must not stay open for minutes. So the plugin is only the client and the work happens elsewhere.

A FastAPI layer accepts the upload, checks the API key and quota, and stores the file in S3-compatible object storage. A ClamAV service scans it before anything else touches it — these are foreign files from foreign WordPress installations. The actual analysis and repair runs asynchronously in a Celery worker with FFmpeg. Postgres holds keys, quotas and session data, Redis is queue and cache, Traefik terminates TLS.

This is not an architecture you would wish on a side project. It follows from the requirement, which is exactly why it is listed here: every service solves a concrete problem, none is there for its own sake.

The real gate was WordPress.org

The part that says most about the work is not the audio pipeline but getting into the official plugin directory. A review team you have never met examines the code for security issues, correct escaping of every output, capability checks and internationalisation.

The review found things I had not seen — and preparing for it surfaced more. They are named in the changelog: a capability check that verified site-wide upload rights rather than whether the user could edit that specific post; backend messages written into the page without escaping; API keys and webhook tokens rendered as plain text inputs, and therefore visible in every screenshot and screen share.

Those entries are public because that is what a changelog is for. Fixing a security issue silently denies users the chance to judge whether they were affected.

What 36 releases mean

Delivery runs through the same directory WordPress uses for all updates. That means every release reaches existing installations, and a broken release breaks other people’s websites.

Which happened once. In version 1.0.27 a cleanup introduced a function call with a missing argument that crashed the entire WordPress admin on any site with a webhook configured. It was only caught two releases later. The changelog entry names the affected versions, the commit that caused it, and the recommendation to update immediately.

Not pretty — but the honest handling is the point. Ship 36 releases and one of them will be bad. What matters is whether users find out what happened.

What is not claimed here

User numbers. The directory currently reports fewer than ten active installations, and that is not a selling point — presenting it as one would be dishonest. What this project evidences is something else: that a complete service with queue, object storage, virus scanning and billing can be built by one person, taken through an external review, and maintained over months.

What exactly does AudioLinter check?
Loudness against EBU R128 and ITU-R BS.1770-4 — the standard broadcasters and streaming platforms work to. Specifically: integrated loudness in LUFS, true peak, loudness range, and dead air, meaning extended silence in the programme. Anything flagged can be corrected in one step without moving the file out of WordPress.
Why is being in the WordPress.org directory worth mentioning?
Because it is a real gate. The plugin team reviews for security issues, correct escaping of every output, capability checks and internationalisation. A plugin listed there has been looked at by someone other than its author — and the same directory delivers its updates.
Why are your own security bugs in the changelog?
Because that is where they belong. Over time the changelog records a capability check that only looked at site-wide upload rights instead of the specific post, backend messages inserted into the page without escaping, and API keys rendered as plain text inputs. Fixing a security issue silently denies users the chance to judge whether they were affected.
Why eight services for a plugin?
Because the analysis cannot run on the customer's website. Audio processing is compute-heavy and takes longer than an HTTP request should wait — so an API accepts the file, stores it in object storage, has a virus scanner check it, and hands the actual work to a worker. Database, queue and reverse proxy follow from that.

← All work