Pulls articles from RSS feeds (and non-RSS pages via CSS selectors), extracts the full article text, merges multi-page articles into one, and bundles everything into a single EPUB "periodical" that's emailed to your Kindle every morning.
Runs free in the cloud on GitHub Actions — your Mac doesn't need to be on.
config.yaml ──> build_digest.py ──> digest.epub ──> email to @kindle.com
sites fetch + extract one file every morning
- RSS feeds are read with
feedparser; only articles newer thanlookback_hoursare kept. - Non-RSS sites are scraped: give an
indexURL and alink_selector(CSS) pointing at the article links. - Full text is extracted with
trafilatura(strips nav/ads/boilerplate). - Paginated articles are stitched together by following
next_page_selectorand concatenating the pages. - Output is one EPUB with a section per source, which Kindle shows as a browsable, paginated document.
Edit config.yaml. Replace the example sources with the sites you
want. Most sites have a feed — try https://SITE/feed, /rss, or /atom.xml.
For sites without one, use the index + link_selector form.
cd kindle-digest
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python build_digest.py --config config.yaml --out digest.epub # build only, no emailOpen digest.epub to check it looks right. To test emailing too:
cp .env.example .env # fill it in, then:
set -a; source .env; set +a
python build_digest.py --send- Create a new GitHub repo and push this folder to it.
- In the repo: Settings → Secrets and variables → Actions → New repository
secret, and add:
SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS,MAIL_FROM,KINDLE_EMAIL(see.env.examplefor what each is). - The job is scheduled in
.github/workflows/digest.ymlfor 11:00 UTC (≈7 AM US Eastern). Change thecronline to your time. - Test it now: Actions → Morning Kindle Digest → Run workflow.
- Find your Send-to-Kindle address: Amazon → Manage Your Content and Devices → Preferences → Personal Document Settings.
- On that same page, add your
MAIL_FROMaddress to Approved Personal Document E-mail List, or Amazon will silently reject the email.
When an article's directly-fetched text comes back shorter than
min_content_chars (i.e. it looks like a paywall teaser), the script
automatically looks up the newest archive.today snapshot
(archive.ph/newest/<url>) and uses that instead. For sites you know are always
paywalled, set archive: always on the source to skip the direct fetch.
- name: The Wall Street Journal
feed: https://feeds.content.dowjones.io/public/rss/RSSWorldNews
archive: alwaysImportant reliability caveat: archive.today sits behind Cloudflare and rate-limits automated traffic (HTTP 429). It works most reliably from a home/ residential IP and is throttled harder from datacenter IPs like GitHub Actions runners. The script retries with backoff and only reads snapshots that already exist (it does not create new ones — that needs a browser/captcha). Expect it to succeed for many but not necessarily all paywalled articles on a given morning. If archive coverage matters a lot to you, the more robust escalation is to run the job from your Mac (residential IP) or add a headless-browser step — ask and I'll wire that up.
Multi-page article that should be merged:
- name: Ars Technica
feed: https://feeds.arstechnica.com/arstechnica/index
next_page_selector: "nav.page-numbers a.next"Site with no feed at all:
- name: Example Blog
index: https://example.com/blog
link_selector: "article h2 a"
max_articles: 5Filter by keyword:
- name: TechCrunch
feed: https://techcrunch.com/feed/
include: ["AI", "startup"]
exclude: ["sponsored"]