From 0bdf2b982b86fb1ff8990fbffd7cd02501271d7f Mon Sep 17 00:00:00 2001 From: Will Estes Date: Fri, 14 Aug 2026 12:33:28 -0400 Subject: [PATCH] Add blog section (4 draft posts) and social post drafts for /platform funnel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continues the content-engine plan: /platform, the homepage link, and the small-business-infrastructure course are already live. This adds the remaining pieces — a content/blog/ section with 4 long-form posts (kept draft:true, so nothing goes live until reviewed) and a committed set of ~12 ready-to-copy social posts in drafts/social-posts.md. Co-Authored-By: Claude Sonnet 5 --- content/blog/_index.md | 4 + .../blog/five-small-businesses-one-server.md | 73 ++++++ ...guardrails-for-running-production-alone.md | 83 +++++++ ...small-businesses-dont-need-a-saas-stack.md | 60 +++++ content/blog/template-dont-fork.md | 56 +++++ drafts/social-posts.md | 219 ++++++++++++++++++ 6 files changed, 495 insertions(+) create mode 100644 content/blog/_index.md create mode 100644 content/blog/five-small-businesses-one-server.md create mode 100644 content/blog/guardrails-for-running-production-alone.md create mode 100644 content/blog/small-businesses-dont-need-a-saas-stack.md create mode 100644 content/blog/template-dont-fork.md create mode 100644 drafts/social-posts.md diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 0000000..595024c --- /dev/null +++ b/content/blog/_index.md @@ -0,0 +1,4 @@ +--- +title: "Blog" +description: "Notes on running production infrastructure for small businesses, one honest mistake at a time." +--- diff --git a/content/blog/five-small-businesses-one-server.md b/content/blog/five-small-businesses-one-server.md new file mode 100644 index 0000000..d4128b5 --- /dev/null +++ b/content/blog/five-small-businesses-one-server.md @@ -0,0 +1,73 @@ +--- +title: "How I Run Infrastructure for Five Small Businesses From One Server" +date: 2026-08-14 +draft: true +description: "A full walkthrough of the hub-and-spoke setup behind five real small businesses — what's shared, what's scoped per brand, and why that split is the whole point." +--- + +Right now, one hub server runs the entire technical backend for five real +small businesses. An author's site. A short-fiction writer's site. A +small-batch soap shop. A literary press running open submissions. A small +press with multiple authors. Different industries, different products, +different customers — same infrastructure underneath, run by one person. + +This is the long version of the story I tell on [/platform](/platform/). +Here's what "one hub, five brands" actually looks like, mechanically. + +## What's on the hub + +One server runs: + +- Mail for every domain — inbound and outbound, for every business, from one + mail platform instead of five. +- The CI/CD pipeline that builds and ships every site, on every push to its + own repo. +- The email courses and campaigns that go out under each business's own + name and voice, from a shared engine that just happens to run five times. +- A shared Postgres database, holding each business's data in its own scope. +- The checkout and fulfillment system behind every book and product sale. +- Continuous monitoring watching all of it, all the time, instead of five + separate someones checking in occasionally. + +Each of those five businesses would otherwise need to find, pay for, and +separately manage their own version of every single one of those pieces — +their own email platform, their own checkout stack, their own hosting +provider, their own monitoring. Here, it's one system, config-scoped per +brand, watched by one person the whole time. + +## What "config-scoped" actually means + +This isn't five businesses crammed into one undifferentiated system hoping +nothing collides. Each business's site, mailing list, and checkout +configuration are separate, brand-specific settings layered on top of the +same underlying engine — not separate codebases someone has to keep in sync +by hand. + +When a real fix is needed — a security patch, a new feature, a bug fix — it +gets written once, verified once, and it's live for every business +immediately. When something is client-specific — their branding, their +content, their pricing — it lives in that client's own configuration, and +nowhere else. + +That split is the entire value proposition: shared reliability, individual +identity. + +## The part that actually makes this safe + +None of the above works if a routine, one-line change to one business's site +could somehow reach into another's, or if "shared server" quietly meant +"shared risk." The thing that makes consolidation safe isn't which machine +everything runs on — it's how tightly every path between the pieces is +scoped. That's a big enough topic on its own that it gets [its own +post](/blog/guardrails-for-running-production-alone/). + +## Is this the right shape for your business? + +If you're currently paying for and separately managing a stack of vendors — +a hosting provider, an email platform, a checkout processor, a project +tool — that have never heard of each other, this is what the alternative +looks like in practice: one coherent system, run by someone personally +accountable for how the pieces fit together. + +If that's where you are, the [5-day email course on /platform](/platform/) +walks through why this approach holds up, one honest mistake at a time. diff --git a/content/blog/guardrails-for-running-production-alone.md b/content/blog/guardrails-for-running-production-alone.md new file mode 100644 index 0000000..7cc6181 --- /dev/null +++ b/content/blog/guardrails-for-running-production-alone.md @@ -0,0 +1,83 @@ +--- +title: "The Guardrails That Let One Person Safely Run Production Email for Five Businesses" +date: 2026-08-14 +draft: true +description: "What actually happens when you're not around and something breaks — the specific, boring mechanisms that make one-person-run infrastructure safe." +--- + +The honest question anyone should ask before trusting one person with their +technical backend is: what happens when you're not around and something +breaks? Not "do you have good intentions" — systems don't run on intentions. +Here are the specific, unglamorous mechanisms that make it safe, today, in +production, for five real businesses. + +## A routine push can never trigger a real send + +Every campaign content repo I run has the same rule built into the tooling +itself, not just into a process someone has to remember: pushing content +changes can only ever produce a *draft* campaign. There's no field, no flag, +no config value that lets a routine content push schedule or trigger an +actual send to real subscribers. + +Sending for real requires a separate, deliberate command — normally +triggered by pushing a dedicated `send/` tag, not a content commit. +If the worst outcome of a typo'd push is "an unsent draft appeared," +the failure mode is designed correctly. + +## Storage that's supposed to be private can't quietly go public + +Every object storage bucket I run defaults to private. There's an automated +check in CI that fails the build if a change would make one publicly +listable — unless there's a deliberate, documented comment explaining +exactly why, right next to the code making the change. Not a wiki policy +somewhere. A build that won't go green until you either fix it or justify it +in writing. + +## One business's access can't reach another's + +Each site I run has its own deploy key, and each key is scoped so narrowly +it can only touch that one site's own files — nothing else on the server, no +other business's credentials or data. If one site's repo were ever +compromised, the blast radius stops at that one site. + +This is worth being specific about, because "shared server" and "shared +risk" get conflated constantly. The thing that actually determines whether +one business's problem can become another's isn't which physical machine +they're on — it's whether access is properly scoped. You can have that +property, or fail to have it, on a dedicated server or a shared one. + +## Anything touching real customer data defaults to "show me," not "do it" + +Every ops script that could affect real subscribers — bulk name backfills, +unsubscribes, list edits — defaults to a dry run. Actually writing a change +requires an explicit `--apply` flag, every time, no exceptions. The safe +path is the path of least resistance; the dangerous one takes an extra, +deliberate step. + +This isn't hypothetical caution. When one client's mailing list switched to +double opt-in partway through, their earlier import batch — people who'd +already agreed to be on the list through a real prior relationship, just not +yet confirmed in the system — stayed exactly as `unconfirmed`, honestly +reflecting what was actually true about them, instead of getting swept into +a clean-looking but false "confirmed" count. Boring and correct beat clean +and wrong. + +## You verify after every step, not just at the end + +Moving a production deploy key off a root account isn't safe to do in one +commit if you're doing it carefully. It took three: move the key to a +dedicated account, verify the deploy still works. Fix the account's shell +so the key actually functions, verify again. Remove the old root-level +access, verify a final time. Three commits, three separate verifications — +because "I'm pretty sure that worked" isn't good enough on a production +system running mail for five businesses. + +## None of this is exotic + +Every mechanism above is the ordinary, unglamorous work of making the +dangerous action harder than the safe one — done once, for the whole +system, instead of relied on per person, per day. That's what "safe to hand +this to someone else" actually means in practice. + +Curious how the whole system fits together? Start at +[/platform](/platform/). diff --git a/content/blog/small-businesses-dont-need-a-saas-stack.md b/content/blog/small-businesses-dont-need-a-saas-stack.md new file mode 100644 index 0000000..005494c --- /dev/null +++ b/content/blog/small-businesses-dont-need-a-saas-stack.md @@ -0,0 +1,60 @@ +--- +title: "Why Small Businesses Don't Need a SaaS Stack" +date: 2026-08-14 +draft: true +description: "The real cost of renting a different tool for every job — and what the alternative looks like once you stop assuming it's the only option." +--- + +Ask most small businesses how their technical backend is put together, and +you'll get some version of the same answer: a checkout processor here, a +storefront platform there, a separate email tool, a separate hosting +provider, maybe a separate project tracker — a different vendor for every +job, each with its own login, its own bill, its own outage schedule, and +none of them aware the others exist. + +That's not a personal failing. It's the default path, because every one of +those tools is easy to sign up for individually and hard to evaluate as a +system. Nobody sits down and designs a five-vendor stack on purpose — it +just accumulates, one "we needed this by Friday" decision at a time. + +## What that actually costs + +The sticker price of each individual tool is rarely the real cost. The real +cost is what happens at the seams: the storefront platform doesn't know +about the email tool's suppression list, so a customer who unsubscribed +from one still gets marketed to by the other. The checkout processor's +receipts don't match the bookkeeping tool's categories, so someone +reconciles them by hand every month. Nobody owns the whole picture, because +no single vendor is responsible for more than their own piece of it. + +And when something breaks at a seam — a webhook silently stops firing, a +sync job quietly drops records — there's often no single person whose job +it is to notice, because it's not clearly any one vendor's fault. + +## The alternative isn't "bigger," it's "coherent" + +The fix for SaaS sprawl usually gets pitched as an enterprise platform — +Salesforce-scale software that can theoretically do everything one tool at +a time was doing. But that trades a pile of small vendors for one giant one, +with all the same seam problems just moved inside a single product's +byzantine configuration, plus an enterprise price tag a small business never +needed. + +The actual alternative is smaller than that: one coherent system — +hosting, email, checkout, project tracking — run as one thing by someone +personally accountable for how the pieces fit together, instead of managed +piecemeal across a stack of vendors who've never heard of each other. Not +bigger. Just not fragmented. + +That's the whole premise behind the way I run infrastructure for five real +small businesses today — one hub, config-scoped per brand, one person +watching the whole system instead of five partial views of five different +pieces. + +## If this sounds like where you are + +If your current setup is "it mostly works, but I'm the one reconciling the +seams by hand," it's worth seeing what the non-fragmented version actually +looks like in practice — not in theory, but running, today, for real +businesses. The [5-day email course on /platform](/platform/) walks through +it, one honest mistake at a time. diff --git a/content/blog/template-dont-fork.md b/content/blog/template-dont-fork.md new file mode 100644 index 0000000..dfdc3ce --- /dev/null +++ b/content/blog/template-dont-fork.md @@ -0,0 +1,56 @@ +--- +title: "Template, Don't Fork: How a New Client Goes Live Without Inheriting Anyone Else's History" +date: 2026-08-14 +draft: true +description: "Why onboarding a new business starts from a genuinely clean slate — and why that's cheap and low-risk instead of a corner cut." +--- + +When a new business signs on, the easy shortcut would be to copy an existing +client's site, swap out the branding, and call it done. It's faster to set +up. It's also a quiet liability: that new site inherits the old one's full +git history, whatever secrets or config might have leaked into old commits, +and a codebase shaped by decisions made for a completely different business. + +Every new site I bring on instead starts from a template, generated fresh — +not forked, not cloned. Zero inherited commit history. Zero leftover config +from whoever came before. The skeleton — the site structure, the shared +tooling — is identical across every client, because it's the proven, +already-working shape. What diverges from day one is content, branding, and +the client's own secrets, nothing else. + +## Why this matters more than it sounds like it should + +Onboarding risk is usually invisible until it isn't. A forked repo carries +forward every decision — good or bad — that was ever made in the original +site's history, plus whatever accumulated in there that nobody remembers is +still there. A generated repo carries forward none of that. The new +business gets exactly the current, proven template, with a clean history +that's theirs alone from commit one. + +It also means a shared tool can be pinned to a specific, tested version per +client, rolled out deliberately, one business at a time — instead of every +site silently inheriting whatever the "source" repo happened to be running +on the day it was forked. + +## What actually diverges between clients + +Looking across the five real businesses running on this setup, what's +different from one to the next is narrow and deliberate: their content, +their branding, their pricing, their own credentials for their own mailing +list. What's identical is the skeleton underneath — the site structure, the +shared engines for email and checkout, the deployment pipeline. + +That's what makes bringing on a new business cheap and low-risk instead of a +bespoke project every time: there's no "figuring out this client's +infrastructure from scratch." There's a proven shape, and a short list of +things that actually need to be decided for them specifically. + +## What this means if you're considering this for your own business + +Onboarding you doesn't mean untangling your business from someone else's +history, and it doesn't mean building your infrastructure from a blank +page either. It means slotting your business into a shape that's already +running, safely, for others — with your own data and credentials scoped +to you, and nothing carried over that shouldn't be. + +More on how the whole system fits together: [/platform](/platform/). diff --git a/drafts/social-posts.md b/drafts/social-posts.md new file mode 100644 index 0000000..7f393df --- /dev/null +++ b/drafts/social-posts.md @@ -0,0 +1,219 @@ +# Social post drafts — /platform launch series + +Ready-to-copy LinkedIn/X posts. Suggested cadence: ~2-3x/week, alternating +technical-guardrail posts (credibility with technical referral partners) and +human/outcome posts (credibility with small-business owners). Posting order +below follows that alternation. Each links to https://reground.org/platform/. + +All claims are sourced from real commits/incidents in this workspace — +sourced-from note under each post for your own reference; drop those notes +before posting. + +--- + +## 1. [Guardrail] The CI check that blocks a public bucket + +I run object storage for five small businesses. Every one of those buckets +defaults to private — and there's a CI check that fails the build if a change +would make one public, unless there's a comment explaining exactly why. + +Not a policy. Not a reminder in a wiki somewhere. A build that won't go green +until you either fix it or justify it in writing, right next to the code that +does it. + +That's the difference between "we're careful about this" and "this system +will not let you be careless about this by accident." + +More on how I run infra for five real businesses off one server → +reground.org/platform + +*(sourced: reground-infrastructure .gitea/workflows/tf-lint.yml + scripts/check-public-buckets.sh, ACL gate on every linode_object_storage_bucket)* + +--- + +## 2. [Human] Meet the five businesses + +Right now, one hub server runs the hosting, email, and checkout for: + +- An author's site +- A short-fiction writer's site +- A small-batch soap shop +- A literary press running open submissions +- A small press with multiple authors + +Same infrastructure underneath. Completely different businesses on top. None +of them think about servers, deploy keys, or database backups — because +that's the whole point. + +Curious what "one hub, five brands" actually looks like under the hood? +reground.org/platform + +*(sourced: reground-site content/platform.md client description; kept anonymized to match how the live page describes them)* + +--- + +## 3. [Guardrail] A `git push` can never send an email + +Every content repo I run campaigns out of has the same rule baked into the +tooling: pushing to the repo can only ever produce a *draft* campaign. There +is no field, no flag, no config value that lets a routine content push +schedule or trigger a real send. + +Sending for real is a separate, deliberate command. Always. On purpose. + +If the worst that can happen from a typo'd commit is "an unsent draft +appeared," you've designed the failure mode correctly. + +reground.org/platform + +*(sourced: eec-campaigns internal/campaign/send.go — Send() only transitions draft/paused → running; sync never touches status)* + +--- + +## 4. [Human] The soap maker doesn't know what a deploy key is + +And that's exactly how it should be. + +One of the five businesses I run infrastructure for makes small-batch +cold-processed soap. She doesn't need to know what a deploy key is, what +Postgres is, or how her checkout flow is hosted. She needs her site up, her +orders processed, and her email list intact. + +The technical decisions underneath are mine to be accountable for, not hers +to worry about. That's the actual service. + +reground.org/platform + +--- + +## 5. [Guardrail] Scoped keys, not shared trust + +Each of the five sites I run has its own deploy key, and each key can only +touch that one site's files — nothing else on the server. It's enforced by +the key itself, not by a policy someone has to remember to follow. + +So if one site's repo were ever compromised, the blast radius is that one +site. Not the other four. Not the shared database. Not the mail server. + +"Scoped" isn't a nice-to-have when you're running multiple businesses from +one place — it's the entire reason it's safe to do at all. + +reground.org/platform + +*(sourced: reground-infrastructure per-site forced-command rrsync deploy keys)* + +--- + +## 6. [Human] Why a literary press's open-submissions inbox matters to me + +One of the businesses I run infra for is a literary press that runs open +submissions — meaning strangers' work lands in their system every week, +sight unseen, from writers hoping someone reads it. + +That's a real trust relationship on both ends: the press trusting their +infrastructure won't lose or leak someone's submission, and the writer +trusting the press with something they wrote. I don't get to see the +manuscripts. I just make sure the plumbing holds. + +reground.org/platform + +--- + +## 7. [Guardrail] Verify after every step, not just at the end + +Moving a production deploy key off the root account isn't a one-commit +change if you're doing it carefully. I did it in three: move the key to a +dedicated account, verify the deploy still works. Fix the account's shell +from a locked-down nologin to bash so the key actually functions, verify +again. Remove the old root-level key, verify a final time. + +Three commits, three separate verifications, because "I'm pretty sure that +worked" isn't good enough on a production mail server for five businesses. + +reground.org/platform + +*(sourced: eec commits 9fa7895 / 4497e4e / b8759d0)* + +--- + +## 8. [Human] A real incident: what "unconfirmed" protected + +One of the five businesses I run switched their list to double opt-in +partway through. Their earlier import batch — people who'd already agreed to +be on the list through a real prior relationship, just not yet +Listmonk-confirmed — could have been swept up and auto-confirmed to make the +numbers look clean. + +Instead, those subscribers stayed exactly as `unconfirmed`, honestly +reflecting what was actually true about them, while the tooling was updated +to explain why. Boring is correct. Correct beats clean. + +reground.org/platform + +*(sourced: reground-tools commit f03c36a, 2026-07-22, Two Squirrels Press double opt-in)* + +--- + +## 9. [Guardrail] Nothing that touches real subscriber data runs by default + +Every ops script I use that could affect real subscribers — bulk name +backfills, unsubscribes, list edits — defaults to "show me what this would +do." Actually doing it requires a separate `--apply` flag, every time, no +exceptions. + +The safe path is the path of least resistance. The dangerous path takes an +extra, deliberate step. That's not a coding style — it's a decision about +what a mistake is allowed to cost. + +reground.org/platform + +*(sourced: reground-tools backfill-names.sh / unsubscribe-subscriber.sh dry-run-by-default, --apply required)* + +--- + +## 10. [Human] "Generate," not "fork" + +Every new client site I bring on starts from a clean slate — zero inherited +git history, zero leftover config from whoever came before them. Not because +forking is technically hard, but because a new client's site shouldn't carry +another business's commit history, secrets, or half-finished experiments +into their own repo on day one. + +Onboarding a new business should feel like a fresh start, because it is one. + +reground.org/platform + +*(sourced: campaigns-template / eec-courses-template — Gitea "Generate Repo" pattern)* + +--- + +## 11. [Guardrail] One engine, five brands, one fix + +When RFC 8058 one-click unsubscribe needed to ship, it went out to every +brand I run at the same time — because it lives in one shared engine, not +five separate forks that would each need the same fix applied by hand, five +times, with five chances to get it slightly wrong. + +That's the actual payoff of "shared engine, per-brand config": a real fix is +one change, verified once, live everywhere. + +reground.org/platform + +*(sourced: eec + bookshop shared RFC 8058 one-click unsubscribe rollout)* + +--- + +## 12. [Human] What used to be four vendors is now one afternoon + +Before this setup, a small business selling books needed a checkout +processor, a separate storefront platform, a separate ebook delivery +service, and a separate email platform — four vendors, four logins, four +bills, none of which talk to each other on their own. + +Now it's one shared service, deployed once, configured per brand, running +identically for every business that uses it. Same reliability, a fraction of +the moving parts. + +reground.org/platform + +*(sourced: bookshop — replaces Stripe + Shopify + BookFunnel + ConvertKit-style stacking)*