prahlaad r.
← All Artifacts
ProjectNotionTooling

Letterboxd → Notion: Bridging Feed-less Apps into a Personal Content Database

Using RSS as a universal adapter to mirror my Letterboxd film diary into a Notion database, a reproducible pattern for turning any app into a feed for your own "second brain," even when it has no official API.

View project →
TL;DR — My Letterboxd film diary mirrors itself into a Notion database automatically, every 6 hours, for $0. The point isn't the films — it's the pattern: any app with no "export to my tools" API can still become a live feed for your own personal database, using RSS as the bridge and GitHub Actions as the engine.

Tools used

  • Letterboxd — the source app, where I log every film I watch. Like most consumer apps, it offers no official API for reading your own data back out.
  • RSS — the bridge. A simple, decades-old XML feed format that Letterboxd quietly publishes for every user. This is the thing that makes the whole project possible without an official API.
  • Python — the glue. A small script parses the feed (feedparser library) and talks to Notion (requests library). A separate scraping library (letterboxdpy) handled the one-time history backfill.
  • Notion API — the destination. A REST API that lets the script create and update rows in my "Watch Log" database.
  • GitHub + GitHub Actions — the host and the engine. The code lives in a GitHub repository; GitHub Actions runs it on a cron schedule (every 6 hours) on free infrastructure — no server to maintain.

What this kind of integration makes possible

  • Own your data — activity from a closed app becomes rows in a database you control: queryable, exportable, durable.
  • One home for everything — films, books, articles, workouts can all flow into the same Notion workspace instead of living in ten separate apps.
  • Zero ongoing effort — once set up, new activity appears on its own. No manual export, no copy-paste.
  • Free and serverless — no hosting bill and no server to patch; the GitHub Actions free tier is enough.
  • Composable — because everything lands in Notion, you can build dashboards, link records across databases, and run your own analytics on your own life.

How to replicate it beyond a movie database

The exact same shape works for almost any app that publishes a feed — only the field mapping changes:

  • Goodreads / StoryGraph → a Reading Log database (feeds of books finished + ratings)
  • Last.fm / Spotify → a Listening database (Last.fm publishes RSS of recent tracks)
  • Strava / fitness apps → a Training Log (many expose .ics or RSS of activities)
  • YouTube → a Watched database (YouTube publishes RSS per channel and per playlist)
  • Podcast apps → an Episodes Heard log
  • Any blog or newsletter → a personal reading inbox

The recipe is identical every time: find the feed → parse it → upsert into Notion → schedule it on GitHub Actions.

What is RSS?

RSS is a plain XML document that an app publishes at a fixed URL, listing recent items in a structured way — for a blog, recent posts; for Letterboxd, recently watched films. It was built in the early 2000s for feed readers, but functionally it's a free, public, read-only API. Many apps still expose one almost by accident, even when they offer no "real" API — which makes it the single most useful thing to look for when you want to bridge an app into your own tools.

How it works

1. Pulling from Letterboxd. Letterboxd publishes my activity at letterboxd.com/<username>/rss/. The script fetches that URL and parses the XML with feedparser. Each feed item already contains the film title, year, my rating, the date watched, a rewatch flag, and my written review — no scraping or login required.

2. Bridging into Notion. For each film, the script calls the Notion API to add or update a row in my "Watch Log" database. It's an upsert: it first looks for an existing row with the same Letterboxd URL — if found, it updates that row; if not, it creates one. That dedup key is what keeps re-runs safe and prevents duplicates.

3. GitHub Actions houses the process. The script lives in a GitHub repo. A small workflow file tells GitHub Actions to run it every 6 hours on a cron schedule — spin up a machine, install dependencies, run the sync, shut down. The Notion token and other secrets live in the repo's encrypted secrets store. There's no server; the entire "deployment" is one 30-line YAML file.

One-time setup also ran two heavier backfills — full diary history, and every film ever marked watched — because the RSS feed only carries the most recent ~50 entries. After that, the 6-hour sync keeps everything current.

Architecture

Letterboxd  (no official personal-data API)
     │
     ├─  RSS feed  ───────────►  sync.py            ──┐
     │   last ~50 entries,       runs every 6h via    │
     │   includes review text    GitHub Actions       │
     │                                                ├──►  Notion
     ├─  diary history  ──────►  backfill.py        ──┤     \"Watch Log\" DB
     │   full history,           one-time             │      244 rows,
     │   scraped library         catch-up             │      deduplicated
     │                                                │
     └─  watched-films  ──────►  backfill_watched.py ──┘
         every film ever         one-time
         marked seen             (superset of diary)

Engineering decisions that made it durable

  • Idempotent upserts — every sync is safe to run twice. Each record is keyed by a canonical Letterboxd URL; the writer does find → update, or create if absent. This is what lets a dumb 6-hour cron job be the entire infrastructure.
  • One canonical key, enforced everywhere — an early bug had the RSS path producing a different key than the backfill path, silently creating 49 duplicate rows on the first scheduled run. The fix: pick one canonical URL shape and make every reader reconstruct it. When multiple writers share a database, the dedup key is a contract.
  • Append-only, never destructive — the sync only adds or updates rows it owns. Manually-created entries (TV shows, films logged directly in Notion) are never touched. Both systems coexist in one database.
  • Tolerant of upstream drift — the scraping library renamed its fields between versions, quietly breaking the backfill. The reader now checks both new and legacy field names. Anything depending on a scraped source should expect its shape to move.

Results

  • 244 rows in the Notion Watch Log, fully populated: title, year, rating, liked status, watched date, review text, poster.
  • 52 dated diary entries + 155 watched-only films + 37 manual entries, coexisting cleanly.
  • Zero-touch — new films logged on Letterboxd appear in Notion within 6 hours, with no action from me.
  • $0/month, no maintenance.

Code & full technical docs: https://github.com/prahlaadr/letterboxd-notion-sync