chore: modernize setup, dependencies, and project docs

This commit is contained in:
Young Lee
2026-02-05 22:34:13 -08:00
parent 6e546d31a0
commit daf54a0fc0
10 changed files with 476 additions and 420 deletions
+81 -112
View File
@@ -1,148 +1,117 @@
# Email-to-RSS
A modern service that turns email newsletters into RSS feeds, built with Cloudflare Workers and ForwardEmail.net. This service provides unique email addresses per feed, a front-end admin panel, and long-term storage of newsletters.
Convert email newsletters into a private RSS feed using Cloudflare Workers + ForwardEmail.
## Why Email to RSS?
This project is self-hosted, uses your own domain, and keeps your data in your own Cloudflare account.
I love consolidating my newsletters into a centralized reading app like [Reeder](https://reederapp.com). They make for a better reading experience and prevents my email inbox from getting clogged with newsletters. However, Reeder requires RSS support and many email newsletters don't support native RSS feeds.
## Why this exists
There are some free services online that do the same thing (e.g. [kill-the-newsletter.com](kill-the-newsletter.com)), but there are several downsides to this approach:
Many newsletters only support email delivery. RSS readers offer a better reading experience, but getting email-only newsletters into RSS usually means relying on shared third-party infrastructure.
- **No long-term retention**: old RSS posts are deleted to save space.
- **Risk of blocklisting**: being forced to use the same domain (@kill-the-newsletter.com) as everyone else increases the likelihood that an email newsletter can blocklist you from signing up.
- **Self-hosting is non-trivial**: Kill The Newsletter is also [open source](https://github.com/leafac/kill-the-newsletter), but the self-hosting steps seem neither straightforward nor does it focus on exclusively leveraging free services.
On the other hand, while Email-to-RSS isn't necessarily a one-click-deploy solution, it works just as well, is customized to your domain, and is **almost** free (you still need to buy a custom domain)!
Email-to-RSS keeps the same workflow while avoiding shared domains and shared data stores.
## Features
- **Autogenerate Custom Emails**: Creates custom email addresses in the format `noun1.noun2.XY@yourdomain.com` for each feed
- **ForwardEmail.net Integration**: Processes incoming emails via webhook with robust IP verification
- **Minimalist Email Parser**: Custom-built lightweight parser that works efficiently in edge environments
- **RSS Feed Generation**: Serves standards-compliant RSS feeds using the modern Feed library
- **Admin Dashboard**: Complete web UI for managing feeds and viewing emails
- **Secure Authentication**: Password-protected admin interface
- **Cloudflare KV Storage**: Efficient, low-cost storage solution for feed data
- **Minimal Dependencies**: Built using modern, web-friendly libraries
- **Lightweight**: Entire worker bundle is optimized for edge deployment
- **Deletion Support**: Email content can be removed from feeds in the admin UI, with automatic cache updates
## Setup
1. Buy your custom domain. You can buy it directly from Cloudflare for convenience. If purchased elsewhere, add the domain to your Cloudflare account to manage the DNS there.
1. **IMPORTANT!** Make sure your domain name extension (e.g. `.com`) is one of the ones allowed by [ForwardEmail.com's free tier](https://forwardemail.net/en/faq#what-domain-name-extensions-can-be-used-for-free).
2. Clone this repository.
3. Open your command line in the cloned repo folder and run: `bash setup.sh` this will install dependencies and set up the Cloudflare Worker/KV with your admin password.
4. Set up your ForwardEmail.net account and configure it to forward to Cloudflare (replace `yourdomain.com` with your custom domain):
1. Sign up for a free account using any email (doesn't necessarily have to be the one used for the newsletter).
2. Add your custom domain.
3. To verify your domain, add the following DNS records to your Cloudflare DNS configuration:
| Type | Name | Content | TTL | Proxy Status | Notes |
| ---- | ---- | -------------------------------------------------- | ---- | ------------ | ---------------------------------------- |
| MX | @ | mx1.forwardemail.net | Auto | DNS only | Set Priority to 10. |
| MX | @ | mx2.forwardemail.net | Auto | DNS only | Set Priority to 10. |
| TXT | @ | "forward-email=https://yourdomain.com/api/inbound" | Auto | DNS only | This forwards your emails to the webhook |
| TXT | @ | "v=spf1 include:spf.forwardemail.net -all" | Auto | DNS only | Email security |
5. Deploy with `npm run deploy`.
6. Go to yourdomain.com to open up the admin panel and log in!
Tip: If you're unsure about any of these steps, ask ChatGPT or Cursor to guide you through them.
- One-click feed creation from an admin dashboard
- Unique newsletter addresses per feed (for example `apple.mountain.42@yourdomain.com`)
- ForwardEmail webhook ingestion with source-IP verification
- RSS generation on demand (`/rss/:feedId`)
- Cloudflare KV storage for feed config + email metadata/content
- Password-protected admin UI
- Fully self-hosted on your Cloudflare account
## Architecture
### Email Flow
1. ForwardEmail forwards inbound messages to `https://yourdomain.com/api/inbound`.
2. The Worker validates the request source against ForwardEmail MX IP ranges.
3. The Worker parses and stores incoming content in KV.
4. `https://yourdomain.com/rss/:feedId` renders RSS from stored items.
5. `/admin` provides feed management and email deletion.
1. A newsletter email arrives at `apple.mountain.42@yourdomain.com` (feed ID format: noun1.noun2.XY).
2. ForwardEmail.net forwards it to your Cloudflare Worker endpoint via webhook.
3. The Worker validates the request is from ForwardEmail.net based on IP address.
4. The email is parsed, content extracted, and stored in Cloudflare KV.
5. Feed metadata is updated to include the new email.
6. The RSS feed endpoint dynamically generates the feed from stored emails.
Main routes:
### Key Components
- `src/routes/inbound.ts`: webhook ingestion
- `src/routes/rss.ts`: RSS rendering
- `src/routes/admin.ts`: admin UI + feed CRUD
- **Email Parser**: Extracts content from ForwardEmail.net webhook payload
- **Feed Generator**: Creates standard-compliant RSS feeds from stored emails
- **Admin UI**: Interface for creating, viewing, and managing feeds
- **ID Generator**: Creates memorable, collision-resistant feed IDs using common nouns
- **Security Layer**: Validates webhook requests against ForwardEmail.net IP addresses
- **Storage Manager**: Organized module for storing and retrieving data from KV
## Requirements
### Code Structure
- Node.js 20+
- A Cloudflare account
- A domain managed in Cloudflare DNS
- A ForwardEmail account
- `src/routes/`: API and UI route handlers for inbound emails, RSS feeds, and admin panel
- `src/utils/`: Utility functions including email parsing, feed generation, and ID creation
- `src/data/`: Data files including the nouns list used in ID generation
- `src/types/`: TypeScript type definitions
- `src/scripts/`: Client-side JavaScript for the admin interface
- `src/styles/`: CSS styling for the admin interface
- `src/index.ts`: Main application entry point with middleware and routing configuration
## Setup
1. Clone this repository.
2. Authenticate Wrangler:
```bash
npx wrangler login
```
3. Run setup:
```bash
bash setup.sh
```
`setup.sh` will:
- install npm dependencies
- verify Cloudflare auth (`wrangler whoami`)
- create KV namespaces (`EMAIL_STORAGE` + preview)
- set the `ADMIN_PASSWORD` secret in `production`
- generate `wrangler.toml` from `wrangler-example.toml`
- stamp `compatibility_date` to the current date
4. Configure ForwardEmail DNS records in Cloudflare:
| Type | Name | Content | Notes |
| ---- | ---- | ---------------------------------------------------- | ----------------------- |
| MX | @ | `mx1.forwardemail.net` | Priority `10`, DNS only |
| MX | @ | `mx2.forwardemail.net` | Priority `10`, DNS only |
| TXT | @ | `"forward-email=https://yourdomain.com/api/inbound"` | webhook target |
| TXT | @ | `"v=spf1 include:spf.forwardemail.net -all"` | SPF |
5. Deploy:
```bash
npm run deploy
```
6. Open `https://yourdomain.com/admin` and sign in.
## Development
This project uses a modern build process with Cloudflare Wrangler's built-in bundling (powered by `esbuild`):
```bash
# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm test
npm run build
# Deploy to Cloudflare
npm run deploy
```
## Technology Stack
## Configuration notes
- **Cloudflare Workers**: Edge computing platform for running the service
- **Cloudflare KV**: Key-value storage for email and feed data
- **Hono**: Lightweight web framework for routing and middleware
- **TypeScript**: Type-safe JavaScript for reliable code
- **Feed**: Modern RSS feed generator
- **Zod**: Schema validation for input data
- `wrangler-example.toml` is the template; `wrangler.toml` is generated locally.
- Keep `compatibility_date` fresh when doing runtime upgrades.
- `ADMIN_PASSWORD` is a Cloudflare Worker secret, not a plain env var in config.
## Minimalist Approach
## Security notes
This project follows a minimalist approach:
- Inbound webhook access is IP-restricted to ForwardEmail MX sources.
- Admin auth is cookie-based (`HttpOnly`, `SameSite=Strict`).
- You should use a strong admin password and rotate periodically.
- No unnecessary dependencies
- Web-standard APIs where possible
- No Node.js-specific modules or polyfills
- Modern TypeScript features
- Clean, maintainable code structure
- Modular organization for improved maintainability
## Upgrading dependencies
## Feed ID System
To refresh dependencies to latest:
The system generates memorable, user-friendly feed IDs in the format `noun1.noun2.XY` where:
```bash
npm outdated
npm install
npm test
npm run build
```
- `noun1` and `noun2` are randomly selected from a curated list of ~450 common, neutral nouns
- `XY` is a random two-digit number between 10 and 99
This is inspired by iCloud's Hide My Email feature.
This format provides:
- Easy to read and share email addresses
- Low collision probability (can handle thousands of feeds)
- Simple to remember for users
- ~20 million possible combinations
### Noun Selection
The noun list has been carefully curated to:
- Include only common, everyday objects and concepts
- Exclude any potentially problematic terms
- Ensure appropriate combinations when nouns are randomly paired
- Maintain a professional appearance for all generated feed IDs
Then update `compatibility_date` and redeploy.
## License