Convert a public Beehiiv newsletter into a standard RSS 2.0 feed.
A lightweight, stateless web service built with FastAPI. Provide any public Beehiiv publication URL and receive a valid RSS feed containing all its articles.
- Accepts a Beehiiv publication URL via query parameter
- Discovers article links on the publication homepage
- Fetches each article and extracts metadata + full HTML content
- Returns a valid RSS 2.0 feed with
content:encodedsupport - Filters out navigation, ads, subscribe forms, and tracking elements
Point any RSS reader (NetNewsWire, Feedly, Miniflux, etc.) at the /feed endpoint instead of requiring users to visit the newsletter website.
// React Native — example with rss-parser (https://www.npmjs.com/package/rss-parser)
import Parser from 'rss-parser';
const parser = new Parser();
const response = await fetch('https://your-server.com/feed?url=https://newsletter.beehiiv.com');
const feed = await parser.parseString(await response.text());
// feed.title, feed.items[0].title, feed.items[0].content, etc.Same approach works for web apps, desktop apps, static site generators, or any HTTP-capable client.
Schedule periodic curl requests to /feed and write the XML to disk (see Using cron under Self-hosting). Serve the files directly from a web server — no Python process needed at read time.
Fetch the RSS server-side and render articles on your site. The content:encoded field contains full cleaned HTML ready to display.
- Does not store anything permanently
- Does not include a scheduler or cron job
- Does not authenticate users
- Does not support non-Beehiiv websites in V1
- Does not use a database, Redis, or any external state
- Does not execute JavaScript
git clone https://github.com/jpaine/beehiiv2rss
cd beehiiv2rss
uv sync # runtime dependencies
uv sync --all-extras # include dev dependencies (pytest, ruff)Start the server:
uv run uvicorn app.main:app --reloadTest it:
curl "http://localhost:8000/feed?url=https://example-newsletter.beehiiv.com"The response is RSS XML with Content-Type: application/rss+xml; charset=utf-8.
curl http://localhost:8000/healthAll configuration is optional. Set these environment variables to override defaults:
| Variable | Default | Description |
|---|---|---|
MAX_ARTICLES |
30 |
Maximum number of articles per feed |
REQUEST_TIMEOUT_SECONDS |
15 |
HTTP request timeout |
MAX_RESPONSE_BYTES |
5000000 |
Maximum response body size |
MAX_REDIRECTS |
5 |
Maximum redirects to follow |
FETCH_CONCURRENCY |
5 |
Concurrent article fetches |
USER_AGENT |
Beehiiv2RSS/0.1 |
HTTP User-Agent header |
LOG_LEVEL |
INFO |
Logging level |
Copy .env.example to .env or export variables directly.
Tests use local HTML fixtures. No live websites required.
uv sync --all-extras
uv run pytestuv run ruff check
uv run ruff format --checkSee DEPLOY.md for a Docker build/run path.
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000The application does not include a scheduler. Run your own cron job to periodically refresh feeds:
*/15 * * * * curl -fsS "http://localhost:8000/feed?url=https://example-newsletter.beehiiv.com" > /path/to/feed.xmlOr configure your news reader to poll the /feed endpoint directly.
Returns RSS 2.0 XML.
Errors return JSON:
| Status | Error | Description |
|---|---|---|
| 400 | invalid_url |
URL is malformed or missing |
| 400 | unsafe_url |
SSRF guard triggered |
| 400 | unsupported_source |
Not a Beehiiv publication |
| 404 | no_articles_found |
No articles could be discovered |
| 422 | parse_error |
Publication could not be parsed |
| 502 | upstream_error |
Upstream website error |
| 504 | upstream_timeout |
Upstream timed out |
| 500 | internal_error |
Unexpected error |
{"status": "ok"}- Only
*.beehiiv.comsubdomains are supported. Custom domains are not supported. - Article discovery relies on the homepage HTML. If Beehiiv changes its page structure, discovery may break.
- Pages rendered entirely by JavaScript are not supported.
- Articles without a clear title are skipped.
- Private IP ranges, localhost, link-local addresses, and carrier-grade NAT space are blocked
- Hostnames are resolved before fetch; all resolved addresses must be public (DNS rebinding protection)
- Homepage URLs, discovered article links, and every redirect hop are validated before outbound HTTP
- Redirect targets must stay on supported Beehiiv domains and pass the same safety checks
- HTTP and HTTPS only
- Cloud metadata hostnames are blocked
- Input validation happens before any HTTP request is made
- Stack traces are never exposed to API users
- Fork the repository
- Create a feature branch
- Make your changes
- Run
ruff checkandruff format - Run
pytest - Submit a pull request
MIT