P8-5 opened with an empty cpanel-backend/ directory and one architectural question: require_once or Composer. On a cPanel shared host the answer resolves once you think it through. Composer wants SSH for remote installs, or a committed vendor/ directory for FTP deploys. SSH is sandboxed or absent on most shared hosting. A committed vendor directory means FTP-uploading hundreds of files on every deploy for zero functional change. The backend’s actual job (accept a subscriber, validate an email, write a row to MySQL, send a confirmation) didn’t justify either.
The result is ~600 lines across 15 files under cpanel-backend/. Zero framework. No autoloader. Six PHP classes loaded via require_once.
The constraint
cPanel shared hosting defines the environment. You get FTP access, a MySQL instance, and a document root. SSH may exist in a sandboxed form; it may not. What you can do reliably is upload PHP files and run them when a request arrives.
Working against this environment with fashionable tooling means fighting it. Laravel on shared hosting is a configuration exercise before it’s a backend. Slim is more tractable, but the deploy story still requires either FTP-uploading the vendor directory or workarounds involving cPanel crons. More moving parts than the problem warrants.
The newsletter backend’s requirements at Phase 8: subscriber intake, email validation, MySQL write, confirmation dispatch. Six endpoints. None of this justifies a framework.
What zero-framework looks like
The layout under cpanel-backend/:
- Entry-point files: one per route
- Class files: one per concern
.htaccess: routes inbound requestsschema.sql: the data model as DDL
Each entry point loads what it needs via require_once. No PSR-4, no class maps, no generated files. When a deploy FTPs a revised class file, the new version is immediately live. PHP doesn’t cache class definitions the way compiled languages cache object files. The deploy is a file copy. The runtime picks it up.
This isn’t a clever architecture. It’s the PHP of 2005. It also runs on every shared host that has shipped PHP in the last twenty years, requires no build step, and fails in ways you can diagnose with error_log(). Boring failure modes are easier to fix than mysterious ones.
Provisioning before code
P8-3 ran before any PHP was written. The sequence: MySQL provisioned, captainrandomco_newsletter database created, two scoped users created (one read/write for the application, one narrower for admin operations), credentials written to a host-side .env outside the document root, keychain mirror set up locally.
The two-user split: if the application credentials are compromised, the blast radius is the app’s own write access. The admin user never enters the codebase. Scoped MySQL users take five minutes to create. Skipping them is false economy.
The host-side .env is a flat file outside the document root, read at runtime via parse_ini_file. Never committed. The keychain mirror keeps credentials in two controlled places: not in code, not in git history.
The schema as documentation
cpanel-backend/schema.sql is 190 lines. Comment density is deliberately high. Column-level comments explain intent, not just type. A reader (including me six months from now) can read schema.sql and understand the data model without cross-referencing application code.
DDL is precise, grep-friendly, and version-controlled. A commented schema outlasts a design document that drifts from the actual tables after the first ALTER TABLE. The schema file became the design documentation; that wasn’t planned, it emerged from the format. Worth doing intentionally from the start on the next project.
The FTP deploy
.github/workflows/deploy-backend.yml is ~115 lines. On push to main, it FTPs the contents of cpanel-backend/ to the document root. The workflow runs. The files land. The endpoint is live.
FTP in 2026 reads as a confession. It isn’t. The alternatives on cPanel shared hosting: the cPanel Git deployment feature has account-configuration overhead and behaves inconsistently across cPanel versions; a Git-triggered pull requires SSH. FTP is the lowest-common-denominator deploy for this tier. The ~115 lines handle credential management, file inclusion, and a post-deploy smoke-check. Boring. Complete.
P8-9 was the milestone: backend deployed and reachable from the wider internet. Not elegant. Done.
The internal preview endpoint
The most recent addition is POST /api/internal/preview-email/. BBBrain’s auto-driver calls this when an article is scheduled. The backend sends a styled HTML preview email for review before publication. Internal only: not public-facing, callable with the right credential in the request.
This is where the framework-free approach carries a visible cost. An equivalent endpoint in Slim would sit behind middleware with built-in request validation. Here, each guard is explicit: check the method, check the credential, check the payload shape, bail on the first failure. More lines than a framework equivalent. Also more legible: every decision is visible in the file, nothing inherited from a base class.
Out of scope for this sprint: abstracting the guard pattern across endpoints. At six endpoints the repetition is acceptable.
What the constraint bought
Six sprints in, the backend has one property a framework-backed alternative probably wouldn’t: it is completely understood. Every line is visible. Every dependency is a PHP built-in. The deploy is a file copy. The data model is a schema.sql with a comment on every column.
The case for a lightweight framework (Slim, Lumen) is real: request/response abstractions, composable middleware, a cleaner testing surface. The case against, on cPanel with FTP deploy: every framework assumption is a potential mismatch with the environment. require_once has no assumptions. It loads a file.
The backend will grow. Campaigns, unsubscribes, delivery tracking are on the roadmap. Whether the framework-free approach stays defensible at higher complexity is a genuine question. The answer at ~600 lines is yes. At 2,000 the answer might change. If it does, the migration is tractable: the schema is clean, the classes are small, the coupling is explicit.
cPanel set the terms. The boring choice fit them. The backend works.
