UNDER THE HOOD
The blog is
also the project.
A working notebook, from the page you read to the jobs that keep it running.
A notebook with a backend
This website is where I write about systems—and where I get to operate one. The reading experience is deliberately simple. The machinery behind conversations, email and moderation is a little more involved.
The public features are now open: 1:1 conversation requests and messages, newsletter subscriptions, and moderated comments on published posts. Posts also have a helpful button. The shared counter at the bottom of this page is a small, working example of persistent state.
Here is what is in place as of 25 September 2026.
Write Markdown, commit, publish
Astro builds the articles and pages from Markdown. Posts live in src/content/posts/; About, Privacy and this page live in src/content/pages/. A draft stays out of the public site until it is ready to publish. I can change the writing without editing a page component or going through an admin editor.
Mermaid diagrams belong in the Markdown too. The build turns them into SVGs, with accessible titles and descriptions, so readers do not have to download a diagram renderer. The notebook styling works in light and dark themes, with legible diagram fonts on mobile.
Commits go to private Forgejo, which mirrors to GitHub. GitHub Actions runs the production workflow, applies database migrations and deploys the Workers and static site. Failed checks stop the release. The footer shows the revision behind the build, so a published page can be traced back to its source.
The same build creates a sitemap, a search index and two RSS feeds: one for posts, and one for explicitly announced page updates. Search runs in the browser. Reading and searching do not require an account.
Static reading, focused APIs
Cloudflare serves the built pages and assets. Requests that need application logic go through a public Worker. Private service bindings connect that entry point to the application backend; the core and jobs Workers do not expose public routes.
There are four Workers: the public website, the application core, the private admin portal and scheduled jobs. D1 stores application records, with a separate database for administrative authentication. SQLite-backed Durable Objects coordinate booking decisions and maintain the shared counter.
D1 is the source of truth for subscription consent and application records. Work waiting to be sent is recorded persistently. Background jobs use bounded retries and delivery states; an uncertain send is not blindly repeated.
Conversations, from request to invitation
The booking form offers 30- or 60-minute conversations, shows times in the visitor’s timezone and checks availability against Google Calendar. Selecting a slot does not reserve it.
After submitting, the visitor gets a request receipt and an email verification step. I review the verified request before confirming it. Approval prepares the Calendar event and Google Meet link; sending the invitation is a separate action. Cloudflare handles booking verification and confirmation emails. Private conversation context stays out of the calendar event and confirmation email.
The admin portal supports cancellation and reconciliation with Calendar, as well as cleanup of eligible request records. A self-hosted ntfy notification tells me when a verified request is ready for review.
Messages, comments and small interactions
The message form sends enquiries to a private inbox and notifies me through ntfy. Sending a message does not book a meeting or subscribe anyone to the newsletter.
Comments on posts go through moderation before appearing publicly. The helpful button records an article reaction, with a browser-held identifier to avoid counting the same retry twice. The counter below uses a Durable Object to keep one shared total across visitors; it is not a visitor counter.
Turnstile, server-side validation and rate limits protect submission paths. The site does not use advertising scripts or visitor analytics. Theme and reaction preferences use browser storage; the privacy page explains those details and the information each feature processes.
A newsletter with explicit consent
Newsletter signup uses email confirmation. Cloudflare sends the verification email; Amazon SES in Mumbai sends the newsletters themselves. Drafting and approving a campaign happen in the private admin portal.
Unsubscribe links, suppression checks, and signed delivery feedback through Amazon SNS are part of the sending path. Hard bounces and complaints stop further sends to the affected address. Open and click tracking are disabled.
The admin tools show delivery outcomes and allow completed or cancelled campaigns to be removed. Subscriber lists are fetched on demand in pages of up to 100, rather than loaded every time I open the dashboard.
The other side of the notebook
The private admin portal uses Pocket ID sign-in and short-lived sessions. Its overview brings together conversations awaiting review, pending comments and the next confirmed conversation. Separate sections manage availability, messages, newsletters, delivery issues and service connections.
Scheduled jobs handle delivery, calendar refresh and retention cleanup. Operational checks look for unavailable public services, overdue work, delivery problems and stale backups. Failure notifications go to my self-hosted ntfy service.
Daily D1 backups are encrypted and stored in a private R2 bucket. Backup verification includes downloading, decrypting and restoring the snapshot into isolated SQLite. That checks that an archive can be read; it does not prove a complete production recovery.
Measuring the running costs
The admin Usage page collects resource measurements scoped to this website, even though my Cloudflare and AWS accounts host other projects. It keeps dated reports and shows missing measurements explicitly.
The cost view models the website using a whole $5 Workers plan allowance: the base fee plus measured overages, with percentages for the included quotas. R2’s free tier and AWS SES charges are distinguished from the Workers allowance. Recorded database storage sizes are shown separately from monthly storage billing.
This is a partial estimate, not my shared-account invoice or a promise of a $5 bill. Billable external Worker requests, monthly database storage costs and some other charges still need reconciliation. Missing data is not treated as zero.
Still on the workbench
The main publishing and interaction features are running. The remaining operations work includes fuller usage history, billing reconciliation and reliable spending alerts. Independent backup copies, independently hosted uptime checks and a provider-side restore and cutover rehearsal also remain to be completed.
I will keep this page updated as those pieces land. The point of treating the blog as a project is to leave a useful record of what works, what fails, and what I change next.
Try changing a number.
A shared total. Add one and see it change across your open tabs.
Loading the counter…