First cut of support for hosting rss.chat on Cloudflare has been checked into cloudflare branch of my fork.
Haven't even kicked the tires yet but at least it's up and public at.
https://rsschat.wizops.workers.dev
PS: Reload after navigation to see the product name displayed. Looks like an issue from moving static JSON based config to database.
PS2: I haven't looked at the code so I don't see why anyone else should. I did however spent some quality time with my coding agent to produce the PLAN.md file so that might be worthwhile reading if anything.
I had my local AI (Ornith) review the plan file and write an article in the voice of someone most of us old bags know (all?) who passed away recently.
A deployment Plan tells you more about how software will ship than any code review. This one had me grumble at three things and actually approve of two.
The single Durable Object for everything. That was my first "wait, really?" moment. The Plan puts every piece — users, items, likes, media metadata, WebSocket state — inside one DO per rss.chat instance. One serverless process on Cloudflare's edge owns it all. It fights the distributed-systems reflex that says every boundary needs a queue between services. There is no queue here; just work happening where it matters.
That only works because Durable Objects aren't microservices in disguise; they're stateful worker processes. The application can have its database next to its logic, the way your grandmother thought was proper before everyone got into microservice nonsense. I'll grumble less as the migration holds.
SQLite on Cloudflare's edge. D1 is SQLite wrapped in HTTP running inside a worker. Not a hand-wavy "managed database." Nearly every line of original SQL carries over verbatim — schema, collate nocase, even triggers from initNewDatabase(). That isn't migration. It's preservation with a slightly different deployment address.
The media split. Media bytes live in R2; metadata lives in D1; a client URL ties them together transparently. You'd make that choice if you'd been burned stuffing binary into relational tables once or twice.
The 15-minute magic token design. This is where I agree with the choices. Persistent emailSecret never rotates on subsequent sign-ins, plus transient one-time tokens in an auth_tokens D1 table with a hard 15-minute TTL. Expired rows are purged automatically by an alarm inside the DO itself — no separate cron job to manage. I've seen magic-link implementations where the window was so tight people had to call their friend for another phone.
What I'd grumble at. The feeds stored in D1 trade write amplification for cache hit ratio across every subscribe query. That's fine until a million subscribers ping /feed ten times a second and you rethink it. And config lives in env vars — config.json with hot reload is gone, replaced by deploy friction. It's the right trade-off if someone ever needs an urgent block/unblock without editing files.
The email fallback chain, though: Cloudflare Email Routing first (no config needed), SendGrid second, Resend third when the others fail — each tried only after the prior falls over — is a real architectural choice mapping failure modes to delivery paths. Most multi-provider deployments mean "first one works or you lose your faith." This one treats the native provider as primary and the rest as operational backup.
The migration does one thing well: it moves the same application onto a fundamentally different execution model — serverless with memory, at the edge — while keeping concerns honest (SQL queries for metadata, object storage for blobs). That's closer to doing it right than most deployments advertised in tech media.
Two marks actually earnable: one for getting something nearly correct on first pass; a smaller one for admitting where things might need rethinking instead of pretending they don't exist. That combination is rarer than anyone wants to believe.
Devorak would argue the feed stuff, and we'd all have to learn about D1 scaling sooner.