What I built
A multi-tenant SaaS, not a landing page with an API call behind it.
Billing, authentication, role separation, rate limiting, audit trails, automated content pipelines and its own SEO surface. Every number below comes from the repository and the live backend.
Integrations I own end to end
- Polar as merchant of record for payments
- Several AI providers behind one fallback chain
- WordPress REST publishing and multi-CMS output
- Pexels and Pixabay for editorial images
- Transactional email, Amazon Associates, Google OAuth and an MCP server
What runs without me
- Scheduled publishing per tenant on pg_cron
- Duplicate topic checks before a credit is spent
- Niche detection for new sites with no posts yet
- Cron and job health checks, so silence is visible
- A dead-letter queue for failed email
I built this alone with AI tooling instead of a team, in months rather than the year a small team would need.
The proof isn't the headcount I avoided. It's the guards, audit trails and fixes on the rest of this page.
The selling page
One topic in, a published post out.
The landing page carries the whole promise in one screen and one action. I cut it from seventeen sections to twelve, removed every secondary call to action and every show-more toggle, and left a single input visitors can use before creating an account.
Fewer clicks, on purpose
Example chips, duplicate calls to action and collapsible sections all showed up as friction. The header button becomes a quiet link once the sticky bar appears, so only one primary action is ever visible.
Mobile is not an afterthought
Every marketing page, tool and dashboard flow got a full responsive pass after I found content cut off on phones. At 390px wide, nothing is clipped and nothing scrolls sideways.
The dashboard
It was a settings maze. Now you land inside the tool.
Funnel data showed people signing up and never generating anything. The old dashboard opened on an overview screen that put navigation between the user and the product. Now the first screen after login is the composer: one question, one button.
What I removed
- The overview screen as the landing page
- Setup before first value: a free sample runs with no site connected
- Advanced options for new users, now behind a Simple and Pro switch
What stays visible
- Which site the article will publish to
- How many articles are left on the plan
- One primary button that says what will happen
Onboarding
Connect a site in about two minutes, or skip it entirely.
New accounts go straight to site connection with a progress indicator, not to a settings page. WordPress publishes directly. Every other platform still gets a finished, ready-to-paste article, so nobody is blocked by their CMS.
Conversion fixes that came from real events, not opinion
| Problem | Fix |
|---|---|
| People signed up and never generated an article | Funnel instrumentation found the exact drop-off step, and a demo article now delivers value before any setup |
| Paying intent was lost during account creation | The chosen plan carries through sign-up, so Get started leads straight to checkout |
| The same promise was repeated three times | Said once, sections cut, one clear action per screen |
Quality in the product
Every article carries its own evidence.
Quality score, target keyword, category, language, word count, image status and publish status all sit on the article row. If an article lands as a draft, the app says why instead of leaving people guessing.
Tiered risk gating
News, finance, politics, health and sport always go to draft for human approval. Evergreen and product content can publish automatically. The policy lives in code, not in a wiki, so it holds when I'm not watching.
Length is a guide, not a quota
Word count has a safety floor and no padding target. Stretching a thin topic to hit a number hurts the whole site, so if a topic can't support the depth, the slot is skipped.
Operating it
I run the same pipeline on my own sites, every day.
Fifty-three articles on this account: forty-two live, eleven held for review. My own sites are the test bed, so if a bug can damage a site, it damages mine before it reaches a customer.
Scheduled publishing went quiet on two of my own sites for weeks, and nothing told me.
The cause was a plan-tier guard misfiring on my own unlimited account, plus a mismatched cron secret returning 401. The fix wasn't only the code. It was a cron health panel, so silence itself now shows up as a failure.
How I ran QA
I was the QA function, not the person hoping it worked.
Production as the test bed
Every feature ran on my own live sites, with my own money, before a customer saw it.
Real behaviour, not guesses
I instrumented the funnel: who signed up, who generated, who connected a site, and who left at which step. Several redesigns came straight from that data.
Adversarial content review
I read output like a hostile editor. That is how I caught the worst class of bug in this product, then turned my checklist into code.
Regression discipline
Every incident left something permanent behind: a guard, a log line, a status check, an audit row or a written rule.
Cross-device sweeps
Full mobile and desktop passes over marketing, pricing, tools and the dashboard, looking for dead ends, not just the happy path.
Observability as a test signal
Structured logging, a failure taxonomy, publish audit trails, a dead-letter view for email and a live status page computed from real failure rates.
The hardest bug wasn't a crash. It was an article that read perfectly and was invented.
No compiler catches that. I caught it by reviewing output adversarially, then encoded the review as automated guards, so the standard holds while I sleep.
Problems and fixes
Content, reliability and security findings, closed.
Content correctness
| Problem | Fix |
|---|---|
| Models invented companies, products, rulings and statistics that read as real | A fabrication guard with three-pass verification, and a hard block on fabricated citations and invented first-hand experience |
| Real facts in false context: the right name with the wrong outcome | Outcome-direction checks on every cited result or ruling |
| Output that was detectable as AI writing | Human-voice constraints, varied sentence rhythm, controlled imperfection and story beats per niche |
| Duplicate topics republished to the same site | Reads the site's existing posts, checks for duplicates before generating and refunds the credit on rejection |
Reliability
| Problem | Fix |
|---|---|
| Generation failing for some accounts | A multi-provider fallback chain, retry logic, a structured failure taxonomy and an incident banner that expires on its own |
| Publishing failing at the CMS with no explanation | A WordPress health check before publishing, a publish audit trail and a clear in-app message when an article lands as a draft |
| Stock image APIs timing out or returning irrelevant photos | Retries with backoff and relevance-scored selection, degrading gracefully instead of breaking the post |
| Duplicate charges or double generations on retry | Idempotency keys on generation and on webhooks |
Security
| Problem | Fix |
|---|---|
| An unsigned JWT fallback in cron authentication | Removed and replaced with a verified shared secret |
| Public database functions callable with any user id | Rewritten to scope to the signed-in user, and unused ones dropped |
| Unbounded AI spend, XSS in rendered article HTML and SSRF on user-supplied URLs | Per-user rate limits with server-side quotas, HTML sanitisation, and URL validation with an allowlist |
| Privilege escalation risk | Roles moved to a dedicated table behind a security-definer check, never on the profile record |
| Vulnerable dependencies | Dependency scanning in the loop, with upgrades applied |