Source & license
Upstream license: MIT
License TL;DR
You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.
Explain MIT in plain English →Summary of the main license. Separate packages and assets can have different terms.
Inspect repository ↗Read this project’s actual license ↗Repository owner
See the upstream repository for the original creator and contributors.
Maintain this project? Maintainer verification →Cloudflare hosting
Free tier eligible within limits
The documented Cloudflare Email RSS deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs.
Hosting requirements
- Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits.
- Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account.
- Inbound Email Routing can use Workers Free, but processing consumes Workers quotas and requires a domain; arbitrary-recipient outbound email requires Workers Paid.
- Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Sources checked 01/10/2026
Repository snapshot: 64f541d. Hosting eligibility reflects the deployment documentation and listed assumptions.
- kill-the-newsletter ↗
Create your own RSS (Atom) feeds from emails, processed and hosted by Cloudflare ([free plan](https://www.cloudflare.com/plans/free/)).
- workers ↗
evelopers.cloudflare.com/workers/wrangler/configuration/ */ { "$schema": "node_modules/wrangler/config-schema.json", "name": "rss-processor", "main": "src/index.ts", "compatibility_date": "2025-09-27", "observability": { "enabled": true }, "workers_dev": false, /** * Smart Placement * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement */ "placement": { "mode": "smart" }, /** * Bindings * Bindings allow your Wor
- r2 ↗
al-time communication and more. * https://developers.cloudflare.com/workers/runtime-apis/bindings/ */ "r2_buckets": [ { "binding": "RSS_BUCKET", "bucket_name": "rss" } ], /** * Environment Variables * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables */ "vars": { "PUSHOVER_URL": "https://api.pushover.net/1/messages.json", "PUSHOVER_TIMEOUT": 1000, "PRETTY_XML": false, "FEED_MAX_ENTRIES": 20, "FEED_MAX_SIZE
- email-workers ↗
# Cloudflare-Email-RSS Create your own RSS (Atom) feeds from emails, processed and hosted by Cloudflare ([free plan](https://www.cloudflare.com/plans/free/)). ## How it works 1. Emails get sent to your configured address on Cloudflare, e.g. `rss-feeds@domain.com` 1. The email gets processed by a Cloudflare worker, each sender address gets it's own feed XML file generated, e.g. `sender@domain.com` -> `sender-domain-com.xml` 1. The feed is stored on Cloudflare R2 and is made accessible, e.g. `https://domain.com/sender-domain-com.xml` 1. An OPML file (`feeds.opml` by default) is maintained in the bucket, keeping a synced list of all feeds 1. Your RSS Reader is configured to fetch this feed file from Clou
- free-tier-eligible ↗
evelopers.cloudflare.com/workers/wrangler/configuration/ */ { "$schema": "node_modules/wrangler/config-schema.json", "name": "rss-processor", "main": "src/index.ts", "compatibility_date": "2025-09-27", "observability": { "enabled": true }, "workers_dev": false, /** * Smart Placement * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement */ "placement": { "mode": "smart" }, /** * Bindings * Bindings allow your Wor
- free-tier-eligible ↗
al-time communication and more. * https://developers.cloudflare.com/workers/runtime-apis/bindings/ */ "r2_buckets": [ { "binding": "RSS_BUCKET", "bucket_name": "rss" } ], /** * Environment Variables * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables */ "vars": { "PUSHOVER_URL": "https://api.pushover.net/1/messages.json", "PUSHOVER_TIMEOUT": 1000, "PRETTY_XML": false, "FEED_MAX_ENTRIES": 20, "FEED_MAX_SIZE
- free-tier-eligible ↗
up>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 seconds)<br> Max of 15 minutes of CPU time per [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) or [Queue Consumer](https://developers.cloudflare.co
- free-tier-eligible ↗
infrequent access storage) for 1.1 GB, you will be billed for 2 GB. ### Free tier You can use the following amount of storage and operations each month for free. | | Free | | --- | --- | | Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month | | Egress (data transfer to Internet) | Free <sup>[1](#user-content-fn-1)</sup> | Caution The free tier only applies to Standard storage, and does not apply to Infrequent Access storage. ### Storage usage Storage is billed using gigabyte-month (GB-month) as the billing metric. A GB-month is calculated by averaging the *peak* storage per day over a billing period (30 days). For examp
- free-tier-eligible ↗
g is based on your Cloudflare plan and email usage. ## Plan pricing Email Routing is available on both the Workers Free and Workers Paid plans. Sending to arbitrary recipients requires the Workers Paid plan. Sending to [verified destination addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#destination-addresses) in your account is free on all plans, including when only Email Routing is configured. | | Workers Free | Workers Paid | | --- | --- | --- | | **Outbound emails (Email Sending)** | Not available | 3,000 included per month, then $0.35 per 1,000 emails | | **Inbound emails (Email Routing)** | Unlimited | Unlimited | The 3,000 included emails apply per a
- MIT ↗
MIT License Copyright (c) [2025] [Rafhaan Shah] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON
- architecture ↗
evelopers.cloudflare.com/workers/wrangler/configuration/ */ { "$schema": "node_modules/wrangler/config-schema.json", "name": "rss-processor", "main": "src/index.ts", "compatibility_date": "2025-09-27", "observability": { "enabled": true }, "workers_dev": false, /** * Smart Placement * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement */ "placement": { "mode": "smart" }, /** * Bindings * Bindings allow your Wor
- architecture ↗
al-time communication and more. * https://developers.cloudflare.com/workers/runtime-apis/bindings/ */ "r2_buckets": [ { "binding": "RSS_BUCKET", "bucket_name": "rss" } ], /** * Environment Variables * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables */ "vars": { "PUSHOVER_URL": "https://api.pushover.net/1/messages.json", "PUSHOVER_TIMEOUT": 1000, "PRETTY_XML": false, "FEED_MAX_ENTRIES": 20, "FEED_MAX_SIZE
What it can replace
Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.
Converting received newsletters into per-sender Atom feeds for a separate reader; newsletter composition, marketing campaigns and outbound sending are excluded.
See supporting source ↗How it works
The shape of Cloudflare Email RSS on Cloudflare, and how it stacks up against the rented tools it replaces.
Architecture
Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.
View upstream source ↗Configuration and workflow sources
Reviewed commit 64f541d8a98f. Files were read as data; upstream applications and CI jobs were not executed.
Deployment configuration · 1 files
Cloudflare Workers · compatibility 2025-09-27
rss-processor · default
Entrypoint: src/index.ts
RSS_BUCKET→ R2
Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.
Runtime source · handlers, binding usage and workflow steps
Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.
- L23 · email handler exported · calls handleEmail
- L29 · handleEmail calls (conditional paths may differ): parser.parse, rawEmail.arrayBuffer, sanitizeEmail, toISOString, sanitizeUrn, getFeed, senderEmail.split, getFeedLink, getDomain, generateUrn, getIconUrl, getDefaultFeedLink, trimEntriesToFit, deleteEntries, getEntryLink, bucket.put, entryLink.push, console.log, addFeedEntry, putFeed
- L128 · sanitizeEmail calls (conditional paths may differ): email.split, address.split
- L137 · addFeedEntry calls (conditional paths may differ): entries.filter, filtered.unshift
- L146 · getFeed calls (conditional paths may differ): bucket.get, obj.text, arrayKeys.has, parser.parse
- L156 · putFeed calls (conditional paths may differ): builder.build, bucket.put
- L167 · sanitizeField calls (conditional paths may differ): field.replace
- L172 · generateUrn calls (conditional paths may differ): sanitizeUrn
- L179 · sanitizeUrn calls (conditional paths may differ): sanitizeField, removedStartEnd.replace
- L185 · getHeader calls (conditional paths may differ): headers.find, h.key.toLowerCase, key.toLowerCase
- L191 · getFeedLink calls (conditional paths may differ): getHeader, sanitizeField
- L221 · getEntryLink calls (conditional paths may differ): getHeader, links.push, sanitizeField
- L242 · getDomain calls (conditional paths may differ): url.match
- L252 · notify calls (conditional paths may differ): form.append, fetch, AbortSignal.timeout, form.toString, console.warn, console.log, String, console.error
- L282 · trimEntriesToFit calls (conditional paths may differ): entries.map, encoder.encode, encodedSizes.reduce, console.log, entries.pop, encodedSizes.pop, removedEntries.push
- L310 · deleteEntries calls (conditional paths may differ): removedEntries.map, entry.id.split, bucket.delete
- L323 · getOpml calls (conditional paths may differ): bucket.get, toISOString, obj.text, parser.parse
- L350 · putOpml calls (conditional paths may differ): toISOString, builder.build, bucket.put
- L358 · syncOpmlWithBucket calls (conditional paths may differ): bucket.list, obj.key.endsWith, feedKeys.add, opml.opml.body.outline.filter, url.startsWith, url.slice, feedKeys.has, opml.opml.body.outline.map, existingUrls.has, getFeed, opml.opml.body.outline.push
Environment references: env.RSS_BUCKET · env.BUCKET_DOMAIN · env.FEED_MAX_SIZE_BYTES · env.FEED_MAX_ENTRIES · env.PRETTY_XML · env.OPML_FILE · env.PUSHOVER_TOKEN · env.PUSHOVER_USER · env.PUSHOVER_DEVICE · env.PUSHOVER_URL · env.PUSHOVER_TIMEOUT
Build and deployment pipeline · 2 GitHub Actions workflows
Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.
Triggers: pull_request
Dependabot Auto-Merge · no job dependencies declared
Condition: ${{ github.actor == 'dependabot[bot]' }}
- Dependabot metadata
dependabot/fetch-metadata@v3.1.0 - Enable auto-merge for Dependabot PRs
gh pr merge --auto --squash "$PR_URL"
Triggers: push, pull_request
build · no job dependencies declared
- actions/checkout@v7
actions/checkout@v7 - Use Node.js ${{ matrix.node-version }}
actions/setup-node@v7 - Shell command
npm ci - Shell command
npm test
deploy: wrangler deploy
Repository README
View original on GitHub ↗Full upstream document by @RafhaanShah · README.md · snapshot 64f541d
Cloudflare-Email-RSS
Create your own RSS (Atom) feeds from emails, processed and hosted by Cloudflare (free plan).
How it works
- Emails get sent to your configured address on Cloudflare, e.g.
rss-feeds@domain.com - The email gets processed by a Cloudflare worker, each sender address gets it's own feed XML file generated, e.g.
sender@domain.com->sender-domain-com.xml - The feed is stored on Cloudflare R2 and is made accessible, e.g.
https://domain.com/sender-domain-com.xml - An OPML file (
feeds.opmlby default) is maintained in the bucket, keeping a synced list of all feeds - Your RSS Reader is configured to fetch this feed file from Cloudflare R2 periodically, or import the OPML file to subscribe to all feeds at once
What you can do with this
- Setup email forwarding rules so all your email newsletters (e.g. Substack) gets forwarded to your Cloudflare address or use your Cloudflare address to sign up
- (Optional) Configure your self-hosted RSS Aggregator (e.g. FreshRSS) to sync with the generated OPML to fetch all your feeds and keep read / unread status tracked
- Use a mobile RSS Reader (e.g. FeedMe) to sync with FreshRSS / Cloudflare Bucket to download and read your articles
Cloudflare Setup
- A Cloudflare Account and Domain (needed for email routing)
- Create Security Rules to control access to your bucket (e.g. limited to your IP / Country / Header / Agent etc)
- Create a R2 Bucket with Public Access (this should still be protected by your previously created Security Rules)
- Create a Worker, you can link it to your fork of this repo or clone this repo and deploy it manually
- Set values for the required Secrets, see .env.example
- Enable Email Routing, and create a new routing address and rule, and link it to the previously created Worker
- Use the email address you configured in the Email Routing rule to sign up for newsletters, or forward from your usual email address
- Configure Billing Notifications for when you are hitting the limits of the free plan, for R2 and Workers
Development
- The project uses Cloudflare Wrangler on the CLI
- Run:
npx wrangler dev - Test:
npx vitest - Deploy:
npx wrangler deploy - Manage Secrets:
npx wrangler secret - Generate Types:
npx wrangler types
License
Frequently asked about Cloudflare Email RSS
What is Cloudflare Email RSS?+
Cloudflare Email RSS is a self-hosted Kill the Newsletter alternative built on the Cloudflare developer platform. Turn incoming newsletters into Atom feeds stored on Cloudflare R2.
What does Cloudflare Email RSS replace?+
Cloudflare Email RSS is listed as an alternative to Kill the Newsletter. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does Cloudflare Email RSS use?+
Cloudflare Email RSS is built on Email Workers, R2, Workers.
How much does Cloudflare Email RSS cost to run?+
The documented Cloudflare Email RSS deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs. Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits. Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account. Inbound Email Routing can use Workers Free, but processing consumes Workers quotas and requires a domain; arbitrary-recipient outbound email requires Workers Paid. Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations. Check current Cloudflare pricing before deploying.
Is Cloudflare Email RSS open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/RafhaanShah/Cloudflare-Email-RSS/64f541d8a98f5be4cb7a73f8a7125f451f944a63/LICENSE. Source code and contributor credit are available at https://github.com/RafhaanShah/Cloudflare-Email-RSS.

Discussion · 0
sign in to comment →