Engineering · October 9, 2026 · 6 min read

How Ship & Shout runs on a $0 stack

Ship & Shout reads a maker’s commits and revenue every week and drafts a build-in-public post with a chart. It has live billing, OAuth to several providers, an LLM in the hot path and a public demo, and its infrastructure bill is $0. The domain is the only thing we pay for. Here’s the stack, and the lessons that cost us time instead of money.

The stack

  • Next.js 15 on Vercel Hobby. App Router, server actions, static pages wherever possible. Functions run in fra1, next to the database.
  • Supabase free tier. Postgres with Row Level Security on every table, Auth for GitHub/Google/email sign-in, Storage for chart PNGs. Drizzle ORM on top.
  • A free-tier LLM fallback chain. One OpenAI-compatible client, eight endpoints tried in order: three Groq models, three OpenRouter free models, two Cerebras models. Each has its own key and rate limit, so free quotas stack. If every one fails, a deterministic template writes the post from the real numbers. The demo never shows an error just because an LLM was busy.
  • Charts without a browser. satori turns JSX into SVG and resvg turns that into a 1200×675 PNG, inside a serverless function.
  • Cron for free. Hobby allows one daily Vercel cron, so a GitHub Actions workflow calls the same idempotent endpoint “hourly”, and Vercel’s daily run is the backstop.
  • Email: Resend’s free tier, falling back to plain SMTP on a mailbox we already had. Billing: Lemon Squeezy, which only takes a cut when someone pays.

Lesson 1: serverless + Postgres needs the transaction pooler

Our first production outage was EMAXCONNSESSION: every function instance held its own session on Supabase’s session pooler (port 5432) until the pool ran dry. Moving the app to the transaction pooler (port 6543), reusing one client per instance with max: 1, and keeping the direct URL for migrations only fixed it.

A follow-up bug: a timed-out query kept owning that single socket, so the instance was wedged until it recycled. Now a query timeout throws the client away and opens a fresh one.

Lesson 2: put the functions next to the database

Public timeline pages took about 2 seconds. Nothing was slow on its own: functions ran in Vercel’s default US region and the database lives in Frankfurt, so every query crossed the Atlantic. One line in vercel.json ("regions": ["fra1"]) took the page to about 0.2 seconds. Check the x-vercel-id response header first: it tells you where your function ran.

Lesson 3: free LLMs are fine if you never depend on one

Free tiers rate-limit, change models and go down. Treating them as a chain made that a non-event: try the next endpoint on a 429 or timeout, stop after a time budget, and always have the template fallback. One call per draft, JSON output, validated with Zod before anything reaches a user. The free tool at /try runs on exactly this chain.

Lesson 4: your bundler doesn’t know about your WASM

Charts worked locally and failed on Vercel with ENOENT: satori loads a HarfBuzz .wasm file and fonts at runtime, and because satori is an external package, Next’s file tracing never copied them. The fix was outputFileTracingIncludes in next.config, then checking the route’s .nft.json to confirm the files were in it.

Lesson 5: “hourly” GitHub Actions cron isn’t hourly

Our hourly workflow actually fires about every 6–7 hours: GitHub documents that scheduled workflows can be delayed or dropped under load, and for us that is the norm. Because the job is idempotent (a user is due from their delivery slot until their draft exists), a late run only delays a draft and never skips a week. If you need real hourly timing, use an external pinger. If you can design for “eventually”, free cron is enough.

What $0 doesn’t buy

Headroom. Free tiers are generous until a launch spike, so the demo has per-IP and daily caps and caches popular repos for an hour. The next step up is clear: a paid LLM key, then a paid Supabase plan. We’ll pay for those when users need them, not before.

Ship & Shout turns your week of commits (and revenue, if you want) into an honest post you approve before it goes anywhere. Try it on any public repo, no account needed. Also on the blog: how often indie makers actually ship.