Compare commits

...

4 Commits

Author SHA1 Message Date
will 344dc4719b Add Plausible Analytics tracking
deploy / deploy (push) Successful in 11s
Wires the site up to the newly-deployed self-hosted Plausible instance
at plausible.reground.org, gated behind hugo.IsProduction so local dev
builds never send events.
2026-08-21 13:23:58 -04:00
will 1d7635a1ce Break up an em-dash in Colleen landing page's credibility bullet
deploy / deploy (push) Successful in 6s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 11:08:24 -04:00
will bd784619bc Add Colleen partnership gift-guide landing/thanks pages
Part of the BOPA list swap with Colleen McCann (Love & Magic Mentoring):
opt-in page bridged for sensitive/intuitive writers, delivery page linking
to the uploaded PDF.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 10:22:16 -04:00
will 0bdf2b982b Add blog section (4 draft posts) and social post drafts for /platform funnel
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 <noreply@anthropic.com>
2026-08-14 12:33:28 -04:00
12 changed files with 549 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
---
title: "Blog"
description: "Notes on running production infrastructure for small businesses, one honest mistake at a time."
---
@@ -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.
@@ -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/<slug>` 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/).
@@ -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.
+56
View File
@@ -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/).
+8
View File
@@ -0,0 +1,8 @@
---
title: "Here's your PDF"
description: "Download The Quiet Way to Reach the Readers Who Actually Need Your Story."
---
Here's your copy of *The Quiet Way to Reach the Readers Who Actually Need Your Story.*
[Download the PDF](https://downloads.reground.org/colleen/colleen.pdf)
+39
View File
@@ -0,0 +1,39 @@
---
title: "The Quiet Way to Reach the Readers Who Actually Need Your Story"
description: "For sensitive, intuitive writers who've been told to hustle louder: three concrete steps to find the readers who already need your story, without turning up the noise."
type: "courses"
eec_slug: "colleen"
# Optional italic line under the title, for handling an objection up front.
hero_subhead: "You don't need a bigger microphone. You need the one reader who's already looking for you."
# Optional image shown in the hero, below the subhead.
hero_image: "/images/gift-colleen.png"
# Optional label on every "Send me day one" button across the page.
cta_text: "Send me the PDF"
# Optional "who's teaching this and why" section. Both fields are independently
# optional — set either or both.
credibility_heading: "Written by Will Estes, who's..."
credibility_bullets:
- "Noticed that most advice on selling your book is marketing advice about hustling louder."
- "Believes that if there's one reader who needs your story, there's more than one. You just have to find them, not shout until they find you."
# Optional curriculum preview.
curriculum_heading: "Here's what's inside"
curriculum_intro: "Three short steps, not another growth-hacking checklist."
curriculum_items:
- label: "Step 1."
text: "Find the review that proves someone already needed your story."
- label: "Step 2."
text: "Thank that reader in a way every future reader can see."
- label: "Step 3."
text: "Share what they connected with so the next reader who needs it can find it."
# Optional heading above the repeated signup form at the bottom of the page.
repeat_cta_heading: "Ready to find the readers who need your story?"
# Optional override of the enroll form's post-submit message (rendered as markdown).
success_message: "Your pdf will be in your inbox in a few minutes. Can't find it? [Grab it here](/gift/colleen-thanks)."
---
+219
View File
@@ -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)*
+1
View File
@@ -13,6 +13,7 @@ disableKinds = ["taxonomy", "term"]
description = "DevOps consulting, ghostwriting, and helping fiction authors find their right readers"
author = "Will Estes"
eec_base_url = "https://eec.reground.org"
plausible_domain = "reground.org"
# [[menu.main]]
# name = "Courses"
+1
View File
@@ -15,6 +15,7 @@
{{ else }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
{{ end }}
{{ partial "head-analytics.html" . }}
</head>
<body>
<a class="skip-link" href="#main-content">Skip to content</a>
+5
View File
@@ -0,0 +1,5 @@
{{ if hugo.IsProduction }}
{{ with .Site.Params.plausible_domain }}
<script defer data-domain="{{ . }}" src="https://plausible.reground.org/js/script.js"></script>
{{ end }}
{{ end }}
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB