Open Source · MIT · Free Tier

Turn email newsletters into private RSS feeds

Self-hosted on Cloudflare Workers. Your data stays in your own account, served from your own domain.

Try the demo Self-host ↓ GitHub

Try it live — no setup required

A hosted instance runs at demo.kill-the.news, pre-loaded with sample feeds. Create a feed, grab the RSS URL, and add it to your reader — all without deploying anything.

URL demo.kill-the.news/admin · Password password
Open demo Resets periodically · for testing only

Everything you need, nothing you don't

Built on serverless infrastructure — zero servers to maintain, no subscription fees.

Self-Hosted & Private

Your emails and feeds live exclusively in your own Cloudflare account. No shared infrastructure, no data mining.

Free Cloudflare Tier

Cloudflare Workers, KV, and Email Routing all fall within the generous free tier. Deploy at zero cost.

Your Own Domain

Subscribe to newsletters using addresses on your own domain (e.g. apple@yourdomain.com). No lock-in.

Two Ingestion Methods

Use Cloudflare Email Routing (no third-party) or ForwardEmail webhooks — whichever fits your setup.

Attachment Enclosures

Email attachments are stored in Cloudflare R2 and exposed as RSS enclosures — no extra hosting needed.

External Auth Support

Optionally delegate admin authentication to Authelia, Authentik, or any reverse proxy that sets Remote-User.

Three steps, done

From email delivery to your RSS reader in milliseconds, with no moving parts.

1

Subscribe with your address

Create a feed in the admin UI and get a unique address like newsletter.42@yourdomain.com. Subscribe to any newsletter with it.

2

Worker ingests the email

When a newsletter arrives, Cloudflare routes it to your Worker. It parses the content and stores it in KV — attachments go to R2.

3

Read in your RSS reader

Your feed is live at /rss/:feedId. Add it to any RSS client and never miss an issue.

Up and running in minutes

A single setup script handles KV namespaces, secrets, and wrangler.toml generation.

Terminal
# 1. Clone the repo
$ git clone https://github.com/juherr/kill-the-news.git && cd kill-the-news

# 2. Log in to Cloudflare
$ npx wrangler login

# 3. Run the interactive setup (creates KV, sets secrets, writes wrangler.toml)
$ bash setup.sh

# 4. Deploy to the edge
$ npm run deploy

Then choose how emails reach your Worker:

Alternative

ForwardEmail Webhook

Point ForwardEmail MX records at your domain. ForwardEmail parses incoming mail and POSTs a JSON payload to /api/inbound.

Deploy to Cloudflare

Everything you need to self-host kill-the-news on your own Cloudflare account, step by step.

1

Prerequisites

You need a free Cloudflare account with a domain managed by Cloudflare DNS, and Node.js ≥ 18.

  • Cloudflare account (free tier is enough)
  • A domain with DNS managed by Cloudflare
  • Node.js ≥ 18 & npm
  • Git
2

Clone & authenticate

Clone the repo and log in to Cloudflare via Wrangler. This opens a browser window to authorize the CLI.

Terminal
$ git clone https://github.com/juherr/kill-the-news.git
$ cd kill-the-news
$ npm install
$ npx wrangler login
3

Run the setup script

The interactive setup script creates the KV namespace, sets the admin password secret, and writes wrangler.toml for you.

Terminal
$ bash setup.sh

# The script will ask for:
#   - Your domain (e.g. newsletters.example.com)
#   - An admin password (stored as a Wrangler secret)
#   - Whether to enable R2 for attachments (optional)

This generates wrangler.toml from the example template. Do not commit it — it is gitignored.

4

Deploy the Worker

Terminal
$ npm run deploy

# Wrangler compiles the Worker, uploads it to Cloudflare,
# and prints the Worker URL. Your admin UI will be live at:
#   https://<your-domain>/admin
5

Configure email ingestion

Choose how incoming emails reach your Worker:

Cloudflare Email Routing

In the Cloudflare dashboard, go to Email → Email Routing and enable it on your domain. Add a catch-all rule with action Send to Worker pointing to your deployed Worker. No third-party service required.

Alternative

ForwardEmail Webhook

Point ForwardEmail MX records at your domain. ForwardEmail parses incoming mail and POSTs a JSON payload to /api/inbound. Useful if you already use ForwardEmail.

6

Optional: enable attachment storage

To store email attachments and expose them as RSS enclosures, create an R2 bucket and bind it in wrangler.toml:

wrangler.toml
[[r2_buckets]]
binding = "ATTACHMENT_BUCKET"
bucket_name = "kill-the-news-attachments"

Attachments will be served at /files/:id/:filename and linked as <enclosure> elements in the RSS feed.

7

Harden with WAF rate limiting

Protect your endpoints against abuse using Cloudflare WAF custom rate-limiting rules — no code changes required, pure infrastructure.

Via Cloudflare Dashboard

Go to Security → Security rules, click Create rule, choose Rate limiting rule, and create one rule per endpoint below.

⚠️ Free tier limitations: only 1 rate limiting rule allowed; period and block duration capped at 10 seconds. Prioritise the /api/inbound rule — it's the public-facing attack surface. Upgrade to a paid plan for full coverage.

Terraform

Via Terraform

Use cloudflare_ruleset with phase = "http_ratelimit" — see the snippet below.

Recommended limits:

WAF rules
EndpointCondition (URI Path)Limit (recommended)Limit (free tier)Action (recommended)Action (free tier)
/api/inbound wildcard /api/inbound/* 60 req / min / IP 10 req / 10 s / IP Block (1 min) Block (10 s)
/admin* wildcard /admin/* 20 req / min / IP 20 req / 10 s / IP Managed Challenge (5 min) Managed Challenge (10 s)

Terraform equivalent (supports method filtering and longer periods — requires a paid Cloudflare plan):

main.tf
resource "cloudflare_ruleset" "rate_limiting" {
  zone_id = var.cloudflare_zone_id
  name    = "kill-the-news rate limiting"
  kind    = "zone"
  phase   = "http_ratelimit"

  rules {
    description = "Rate limit /api/inbound"
    expression  = "(http.request.uri.path eq \"/api/inbound\" and http.request.method eq \"POST\")"
    action      = "block"
    ratelimit {
      characteristics     = ["ip.src"]
      period              = 60
      requests_per_period = 60
      mitigation_timeout  = 60
    }
  }

  rules {
    description = "Rate limit /admin"
    expression  = "starts_with(http.request.uri.path, \"/admin\")"
    action      = "managed_challenge"
    ratelimit {
      characteristics     = ["ip.src"]
      period              = 60
      requests_per_period = 20
      mitigation_timeout  = 300
    }
  }
}

These rules run at the Cloudflare edge before the Worker is invoked — zero latency impact on normal traffic. If ForwardEmail's delivery IPs ever trigger the /api/inbound limit, add them as an IP Access Rule with action Allow under Security → WAF → Tools.

Built on reliable primitives

Minimal dependencies, maximum portability — runs entirely on Cloudflare's global network.

Cloudflare Workers Hono KV Storage R2 Object Storage TypeScript RSS 2.0 Atom postal-mime Zod Vitest

kill-the-news is free and open source. If it saves you time, consider supporting its development.

Sponsor on GitHub