Jfreaks Software Solutions, Jul 2025 to Mar 2026
Netus.ai
Jfreaks Software Solutions, July 2025 to March 2026, in a shared team codebase. I owned billing, credits, the public tools API and two Go side services, and led the Astro marketing site (400 of its 790 commits).
Credits were debited in one order everywhere: one-off packs first, then the subscription, then personal credits.
An AI writing and paraphrasing platform with a Go and MySQL backend and an Astro marketing site.


Go, MySQL, Redis, Astro 5, React, Stripe, Creem, AWS SES, Ghost, Kubernetes
Problem
Subscription state was written from the client after checkout, so a failed redirect meant a paying user without credits. Plans were hard-coded in environment variables. The marketing site could not let visitors try the tools without an account, and blog and newsletter workflows depended on WordPress. The team also needed a video downloader and a newsletter sender that would not block the main API.
Hardest problem
Paying users who never got their credits. Subscription state was written from the browser after checkout, so any interrupted redirect left a paying user with nothing. I moved ownership of that state to the payment providers: Stripe webhooks, and later Creem (a second payment provider), create, renew and cancel subscriptions behind a unique constraint and an upsert on the subscription ID, so redeliveries and racing events land in one row. The browser redirect no longer writes subscription state at all.
What I built
- Moved billing onto payment-provider webhooks (checkout, subscription updated and deleted, invoice paid and failed), later extended to Creem past-due and renewal events
- A plans table mapping Stripe price IDs and Creem product IDs to credit allowances and intervals, replacing hard-coded mappings, with a Go backfill tool for existing subscriptions
- Pay-as-you-go credit packs: Stripe Checkout in payment mode with tiered pricing, a six-month expiry, and a layered deduction engine used by every tool
- Redis in the backend, with a Lua-scripted daily and per-minute rate limiter, and a public API that let anonymous visitors try a dozen tools under per-tool daily limits
- Failed-payment reminder emails (dunning) on an escalating schedule, keyed by invoice and cleared on renewal, and a Kubernetes CronJob in Go that reset free-tier credits each month
- Two Go side services: a newsletter sender fed by Ghost webhooks, and a video download service extracted from the monolith
- Most of the Astro 5 marketing site: product pages, pricing, cookie consent, support form, JSON-LD and the public React tool islands; the blog move from WordPress to Ghost; and a post-build step that converts every page to Markdown (plus llms.txt) for LLM crawlers
Engineering decisions
- Credit deduction ran in one fixed order across every AI tool: one-off packs, then subscription credits, then personal credits, with partial-cover arithmetic when a pack ran out mid-request
- The rate limiter is a single Lua script (INCR, then EXPIRE only when the count is one), so the daily window and the per-minute window are each set atomically. It fails open, so a Redis outage lets requests through instead of blocking paying users
- Guest access reused one HttpOnly cookie across netus.ai and vd.netus.ai. The video service verified the same JWT, so a visitor's daily quota followed them between services
- Dunning counters lived in Redis with a 21-day TTL and sent at attempts one, three, five and seven, so retry storms from the payment provider did not spam the customer
- The newsletter digest is two stages: the scheduler only pushes a date onto a Redis list; a BRPOP worker does the send, in batches of 500 under the provider's per-second limit. A restart between the two loses nothing
In production
- Anonymous visitors could try the paraphraser, detector, summariser and nine generators without an account, each under its own daily limit
- The newsletter, video download and credit reset ran as their own Kubernetes workloads, outside the main API
Request flow
- 1.A visitor on netus.ai uses a tool. The site checks for a guest cookie; if none, it runs reCAPTCHA v3 and requests a guest token. A low score returns 428 and the client retries with reCAPTCHA v2
- 2.The public route passes through the guest middleware and the Redis rate limiter, which maps the endpoint to a generation type and its daily limit
- 3.For a signed-in user, the same tool route checks credits (one-off, subscription, personal) and refuses with 402 if the sum is short
- 4.The backend builds the prompt, calls Azure OpenAI with a fallback to OpenAI on 429, and post-processes the output
- 5.The generation is stored and credits are debited in order. One-off packs are consumed first
- 6.On renewal, the provider's payment event resets subscription credits from the plans table. Free users are reset by the monthly CronJob
Architecture
A Go REST API on MySQL and Redis serves the React app and the Astro marketing site. Payment-provider webhooks own subscription state; the browser never writes it. Three smaller Go workloads (newsletter, video download, free-credit reset) run beside the API on managed Kubernetes, sharing Redis and the guest-token secret.
Connections
- 1.Site, app ↔ Go API: REST, JWT or guest cookie
- 2.Stripe, Creem → Go API: Webhooks own subscription state (queued or scheduled)
- 3.Go API ↔ MySQL: Prepared SQL, credits in order
- 4.Go API ↔ Redis: Lua rate limits, dunning counters
- 5.Go API ↔ Azure OpenAI: Generate, fall back on 429
- 7.Video service ↔ Redis: Per-minute limit
- 8.Credit reset → MySQL: Reset free credits monthly (queued or scheduled)
Components
- Site, appAstro 5, React
- The Astro 5 marketing site on netus.ai with React islands for every public tool, a Ghost-backed SSR blog, cookie consent, pricing and a Markdown mirror of every page for LLM crawlers. The React app on app.netus.ai for signed-in users, with the credit purchase flow, billing pages and a cancellation-reason form.
- Go APICredits, billing, tools
- Go REST API with two route groups: /api/v1 behind JWT auth and /api/v1/public behind the guest-token middleware and the Redis rate limiter. Serves the AI tools, the layered credit engine, plans, one-off credits, payment webhooks, dunning and support. Prepared statements against MySQL; no ORM.
- Stripe, CreemSigned webhooks
- Signature-verified subscription and invoice events that create, renew, cancel and dun subscriptions. These events, not the checkout redirect, are what write subscription state.
- RedisLimits, dunning, queues
- Redis 7 with an LRU cap: daily and per-minute rate-limit counters, dunning counters, and the newsletter's job lists and sets.
- MySQLPlans, credits, subs
- Users, personal and subscription credits, one-off credits with expiry and a unique checkout session ID, plans keyed by Stripe price and Creem product IDs, subscriptions with a unique provider ID, payments and cancellation reasons.
- Azure OpenAIOpenAI fallback
- Azure OpenAI handles generation, with OpenAI direct as the fallback on rate limits.
- Video serviceGo, yt-dlp
- A chi server around yt-dlp and ffmpeg with the shared guest-token middleware and a per-minute Redis limit. Falls back through alternative download formats, caps files at 500 MB, streams the result and deletes it.
- Credit resetMonthly CronJob
- A Kubernetes CronJob in Go that resets free-tier credits on the first of each month in batches of 1,000.