diff --git a/README.md b/README.md index 87aa7e1..6d69249 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Email-to-RSS -Convert email newsletters into a private RSS feed using Cloudflare Workers + ForwardEmail. +Convert email newsletters into a private RSS feed using Cloudflare Workers. This project is self-hosted, uses your own domain, and keeps your data in your own Cloudflare account. @@ -17,33 +17,46 @@ Email-to-RSS keeps the same workflow while avoiding shared domains and shared da - Inline double-confirm delete interactions with toast feedback in the admin dashboard - Resizable + sortable table columns in the admin dashboard (Table view) - Unique newsletter addresses per feed (for example `apple.mountain.42@yourdomain.com`) -- ForwardEmail webhook ingestion with source-IP verification +- Cloudflare Email Workers ingestion (no third-party service) +- ForwardEmail webhook ingestion with source-IP verification (optional alternative) - Optional per-feed sender allowlist (`email@domain.com` or `domain.com`) - 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 -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. +Two ingestion methods are supported — pick one or use both: + +| Method | How it works | +| ---------------------- | ------------------------------------------------------------------ | +| **Cloudflare Email Workers** | Cloudflare Email Routing delivers the raw message directly to the Worker via the `email()` handler — no outbound webhook needed | +| **ForwardEmail webhook** | ForwardEmail parses the message and POSTs a JSON payload to `POST /api/inbound`; the Worker verifies the source IP before processing | + +Common path: + +1. Incoming email arrives at `user@yourdomain.com`. +2. The Worker resolves the feed from the recipient address and stores the email in KV. +3. `https://yourdomain.com/rss/:feedId` renders RSS from stored items. +4. `/admin` provides feed management and email deletion. Main routes: -- `src/routes/inbound.ts`: webhook ingestion +- `src/lib/cloudflare-email.ts`: Cloudflare Email Workers ingestion +- `src/routes/inbound.ts`: ForwardEmail webhook ingestion - `src/routes/rss.ts`: RSS rendering - `src/routes/admin.ts`: admin UI + feed CRUD ## Requirements - Node.js 20+ -- A Cloudflare account -- A domain managed in Cloudflare DNS -- A ForwardEmail account +- A Cloudflare account (free plan works — Workers, KV, and Email Routing are all included) +- A domain added to Cloudflare as a zone (DNS managed by Cloudflare) +- A ForwardEmail account _(Option B only)_ + +## Cloudflare setup + +If your domain is not yet on Cloudflare: in the [Cloudflare dashboard](https://dash.cloudflare.com/), go to *Add a site*, enter your domain, choose the Free plan, and follow the instructions to update your nameservers at your registrar. Wait for the zone to become active (usually a few minutes). ## Setup @@ -56,17 +69,31 @@ Main routes: ```bash bash setup.sh ``` + The script will prompt for an admin password and your domain, then: + - install npm dependencies + - verify Cloudflare auth (`wrangler whoami`) + - create KV namespaces (`EMAIL_STORAGE` + preview) in your account + - set the `ADMIN_PASSWORD` secret in the `production` environment + - generate `wrangler.toml` from `wrangler-example.toml` with your KV IDs, domain, and today's compatibility date -`setup.sh` will: +4. Configure email ingestion — choose **one** of the two options below. -- 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 +### Option A — Cloudflare Email Workers (recommended) -4. Configure ForwardEmail DNS records in Cloudflare: +No third-party service required. Cloudflare receives the email and hands it directly to the Worker. + +1. In the Cloudflare dashboard, go to *Email → Email Routing* for your zone and click **Enable Email Routing**. Cloudflare will prompt you to add MX and SPF records — accept and it adds them automatically. +2. Under *Email Routing → Routing Rules*, add a **Catch-all** rule: + - Action: **Send to Worker** + - Worker: `email-to-rss` (the name from `wrangler.toml`) + +That's it. No webhook configuration is needed. + +### Option B — ForwardEmail (alternative) + +Use this if you prefer ForwardEmail's additional features (sender filtering, open-tracking, etc.). + +Add these DNS records in Cloudflare (*DNS → Records*): | Type | Name | Content | Notes | | ---- | ---- | ---------------------------------------------------- | ----------------------- | @@ -75,14 +102,20 @@ Main routes: | TXT | @ | `"forward-email=https://yourdomain.com/api/inbound"` | webhook target | | TXT | @ | `"v=spf1 include:spf.forwardemail.net -all"` | SPF | -5. Deploy: +Replace `yourdomain.com` with your actual domain. +The Worker verifies each webhook request against ForwardEmail's published MX IP list before processing it. + +5. Deploy: ```bash npm run deploy ``` + Wrangler will create the Worker and register `yourdomain.com` (and `www.yourdomain.com`) as custom domains pointing to it. Cloudflare handles TLS automatically. 6. Open `https://yourdomain.com/admin` and sign in. +> **Tip:** To verify the Worker is running, check *Workers & Pages → email-to-rss* in the Cloudflare dashboard. The *Custom Domains* tab should list your domain once the deploy succeeds. + ## Development ```bash @@ -100,7 +133,7 @@ npm run build ## Security notes -- Inbound webhook access is IP-restricted to ForwardEmail MX sources. +- When using Option B (ForwardEmail), inbound webhook access is IP-restricted to ForwardEmail MX sources. - Admin auth uses a signed, `HttpOnly`, `Secure`, `SameSite=Strict` cookie. - Admin responses are `no-store` to avoid cache leakage. - For high-value feeds, set `Allowed senders` so only known sender addresses/domains are accepted.