Compare commits
4 Commits
e967ddccbc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 344dc4719b | |||
| 1d7635a1ce | |||
| bd784619bc | |||
| 0bdf2b982b |
@@ -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.
|
||||||
@@ -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/).
|
||||||
@@ -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)
|
||||||
@@ -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)."
|
||||||
|
---
|
||||||
@@ -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)*
|
||||||
@@ -13,6 +13,7 @@ disableKinds = ["taxonomy", "term"]
|
|||||||
description = "DevOps consulting, ghostwriting, and helping fiction authors find their right readers"
|
description = "DevOps consulting, ghostwriting, and helping fiction authors find their right readers"
|
||||||
author = "Will Estes"
|
author = "Will Estes"
|
||||||
eec_base_url = "https://eec.reground.org"
|
eec_base_url = "https://eec.reground.org"
|
||||||
|
plausible_domain = "reground.org"
|
||||||
|
|
||||||
# [[menu.main]]
|
# [[menu.main]]
|
||||||
# name = "Courses"
|
# name = "Courses"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
{{ else }}
|
{{ else }}
|
||||||
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
|
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ partial "head-analytics.html" . }}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a class="skip-link" href="#main-content">Skip to content</a>
|
<a class="skip-link" href="#main-content">Skip to content</a>
|
||||||
|
|||||||
@@ -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 |
Reference in New Issue
Block a user