<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Delta Tools</title>
    <description>The latest articles on DEV Community by Delta Tools (@deltatools).</description>
    <link>https://dev.to/deltatools</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4157800%2Fce41505c-e4f3-4db7-b7c9-195937552227.png</url>
      <title>DEV Community: Delta Tools</title>
      <link>https://dev.to/deltatools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deltatools"/>
    <language>en</language>
    <item>
      <title>How I Built a Substack New-Post Monitor (and the Dedup Problem Nobody Warns You About)</title>
      <dc:creator>Delta Tools</dc:creator>
      <pubDate>Sun, 04 Oct 2026 00:33:16 +0000</pubDate>
      <link>https://dev.to/deltatools/how-i-built-a-substack-new-post-monitor-and-the-dedup-problem-nobody-warns-you-about-29cg</link>
      <guid>https://dev.to/deltatools/how-i-built-a-substack-new-post-monitor-and-the-dedup-problem-nobody-warns-you-about-29cg</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Fetching a Substack publication's latest posts took an afternoon. Making the monitor &lt;em&gt;not alert twice on the same post&lt;/em&gt; — across runs, crashes, and machine moves — was the actual project. Here's the naive version, the three ways it broke, and the dedup design that finally held. The finished tool is our &lt;a href="https://apify.com/delta_tools/substack-new-post-monitor?utm_source=dev.to&amp;amp;utm_medium=article&amp;amp;utm_campaign=substack-how-i-built"&gt;Substack New-Post Monitor&lt;/a&gt; on the Apify Store.&lt;/p&gt;




&lt;h2&gt;
  
  
  The naive version (afternoon one)
&lt;/h2&gt;

&lt;p&gt;The job sounded trivial: watch a Substack publication, tell me when a new post appears. Substack even hands you the data — every publication has an RSS feed at &lt;code&gt;&amp;lt;publication&amp;gt;.substack.com/feed&lt;/code&gt;. Parse it, compare against last time, alert on the difference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;

&lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.substack.com/feed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Latest: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done, right? It printed the latest post. I wired it to a Slack webhook, set a 15-minute cron, and felt extremely productive for about a day.&lt;/p&gt;

&lt;p&gt;Then the double alerts started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break #1: "What did I already see?" has no obvious home
&lt;/h2&gt;

&lt;p&gt;The first version kept &lt;code&gt;seen_ids.json&lt;/code&gt; next to the script. It worked until I moved the script to a different machine and every post re-alerted — the state file didn't come along. Then I forgot the file on a deploy and got a fun burst of "NEW POST" messages for articles from 2023.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;the state file is the product, and it needs to live where the compute lives.&lt;/strong&gt; On Apify, that meant a named key-value store shared across scheduled runs — state travels with the actor, not with my laptop. Anywhere else, it means a real database or at minimum a state file on the same always-on host as the cron job, backed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break #2: The crash between "detect" and "save"
&lt;/h2&gt;

&lt;p&gt;This one was subtler. The original loop was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch feed&lt;/li&gt;
&lt;li&gt;Diff against seen IDs&lt;/li&gt;
&lt;li&gt;Send alerts for new posts&lt;/li&gt;
&lt;li&gt;Save updated seen IDs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the process died between 3 and 4 — machine reboot, OOM kill, deploy — the next run re-detected the same posts and re-alerted. I "fixed" it by saving state &lt;em&gt;before&lt;/em&gt; sending alerts, which flipped the failure mode: crash between save and alert meant &lt;strong&gt;silently missing posts&lt;/strong&gt;, which is worse. A missed alert is invisible; a duplicate is just annoying.&lt;/p&gt;

&lt;p&gt;The correct order is a tiny transaction: fetch → diff → &lt;strong&gt;persist the new seen-set first&lt;/strong&gt; → send alerts → if alerting fails, the next run sees the posts as already-seen... no wait, that drops them again.&lt;/p&gt;

&lt;p&gt;What actually works: persist seen IDs &lt;em&gt;and&lt;/em&gt; a pending-alerts queue atomically, then drain the queue. On startup, drain any leftover queue before doing new work. It's a miniature outbox pattern, and yes, it's overkill for a side project — until the one time your 3am deploy eats the alert for the post you were actually waiting for.&lt;/p&gt;

&lt;p&gt;For the Apify version, the platform's dataset + key-value store gives atomic-ish primitives that make this straightforward. The principle is the same anywhere: &lt;strong&gt;never let "I noticed it" and "I told someone" be separable without a recovery path.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Break #3: Identity is harder than it looks
&lt;/h2&gt;

&lt;p&gt;What identifies "the same post" across polls? My first attempt used the post URL. Fine — until Substack's feed briefly served a post with a different canonical URL during an update, and it re-alerted. Then I tried titles; Substack authors edit titles, and every edit re-alerted.&lt;/p&gt;

&lt;p&gt;RSS entries have GUIDs (&lt;code&gt;entry.id&lt;/code&gt; in feedparser), which are &lt;em&gt;supposed&lt;/em&gt; to be stable identifiers. They mostly are. My prototype scheme: &lt;strong&gt;GUID primary, URL as fallback, and never the title.&lt;/strong&gt; Two identifiers agreeing beats one identifier you trust.&lt;/p&gt;

&lt;p&gt;The shipped version simplified this further: Substack's archive API hands you stable post IDs, so the dedup key is just the post ID. Same principle, less machinery.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the finished actor does
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/delta_tools/substack-new-post-monitor?utm_source=dev.to&amp;amp;utm_medium=article&amp;amp;utm_campaign=substack-how-i-built"&gt;Substack New-Post Monitor&lt;/a&gt; on the Apify Store is this design, packaged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You give it a publication URL and a schedule. It fetches via Substack's public archive API, dedups on Substack's stable post IDs, persists its snapshot in a named key-value store (shared across scheduled runs — the lesson from Break #1, applied), and emits only genuinely new posts to the output dataset.&lt;/li&gt;
&lt;li&gt;New posts land in the dataset ready to wire to alerts via Apify webhooks; a check that finds nothing costs a fraction of a cent.&lt;/li&gt;
&lt;li&gt;It passed 7/7 of my stress tests: duplicate suppression across runs, crash recovery, multi-publication state isolation, feed hiccups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not claiming it's the only way to do this. The 20-line script from my &lt;a href="https://dev.to/deltatools/how-to-get-notified-when-a-substack-publication-posts-something-new-319a"&gt;alerts tutorial&lt;/a&gt; plus a &lt;code&gt;seen_ids.json&lt;/code&gt; on a Raspberry Pi is a completely legitimate setup — I ran exactly that for weeks. The actor is for when you'd rather not be the ops team for your own alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real lesson
&lt;/h2&gt;

&lt;p&gt;Every "monitor X for changes" tool is two tools: a &lt;strong&gt;fetcher&lt;/strong&gt; (afternoon) and a &lt;strong&gt;rememberer&lt;/strong&gt; (the actual project). The fetcher is the demo. The rememberer is the product. If you're building any kind of change-detection — price trackers, job alert bots, listing monitors — budget your time accordingly: 20% fetching, 80% state, identity, and crash recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why not just use Substack's email subscriptions?&lt;/strong&gt;&lt;br&gt;
They work, but they're inbox-bound and all-or-nothing per publication. A monitor routes to Slack/Discord/webhooks and watches without subscribing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it handle paid publications?&lt;/strong&gt;&lt;br&gt;
Only public/free posts via the public feed — same limitation as any RSS-based approach. Paywalled content needs an authenticated session, which is a different tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does it cost to run?&lt;/strong&gt;&lt;br&gt;
On Apify's pricing: a scheduled check that finds nothing costs a fraction of a cent. You pay meaningfully only when there's actually a new post to report. (Exact numbers depend on Apify's current compute pricing.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could I build this myself?&lt;/strong&gt;&lt;br&gt;
Absolutely — the tutorial version is 20 lines plus a state file. Build it if you enjoy the ops; use the actor if you don't.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Get Notified When a Substack Publication Posts Something New</title>
      <dc:creator>Delta Tools</dc:creator>
      <pubDate>Sun, 04 Oct 2026 00:30:36 +0000</pubDate>
      <link>https://dev.to/deltatools/how-to-get-notified-when-a-substack-publication-posts-something-new-319a</link>
      <guid>https://dev.to/deltatools/how-to-get-notified-when-a-substack-publication-posts-something-new-319a</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Every Substack publication has a hidden RSS feed at &lt;code&gt;publicationname.substack.com/feed&lt;/code&gt;. You can watch it with an RSS reader, poll it with a 20-line Python script, or — if you want alerts without running infrastructure — use a scheduled monitor like our &lt;a href="https://apify.com/delta_tools/substack-new-post-monitor?utm_source=dev.to&amp;amp;utm_medium=article&amp;amp;utm_campaign=substack-alerts-tutorial"&gt;Substack New-Post Monitor&lt;/a&gt;. Below are all three methods, working code included.&lt;/p&gt;




&lt;p&gt;I follow about a dozen Substack publications. For months my system was "remember to check them," which is to say I missed half of everything. Substack has email subscriptions, but I didn't want a dozen newsletters clogging my inbox — I wanted a ping only when something new went live, routed where I actually look.&lt;/p&gt;

&lt;p&gt;There are three solid ways to do this. They go from simplest to most automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: The hidden RSS feed + a reader (2 minutes, free)
&lt;/h2&gt;

&lt;p&gt;This is the thing most people don't know: &lt;strong&gt;every Substack publication exposes an RSS feed&lt;/strong&gt; at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;publication&amp;gt;.substack.com/feed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if you follow &lt;code&gt;stratechery.substack.com&lt;/code&gt;, the feed is &lt;code&gt;https://stratechery.substack.com/feed&lt;/code&gt;. It contains the latest posts with titles, links, and timestamps. It just works — no API key, no scraping.&lt;/p&gt;

&lt;p&gt;Point any RSS reader at it (Feedly, Inoreader, NetNewsWire, even an old-school reader). You'll see new posts alongside everything else you follow. Zero code, zero cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downside:&lt;/strong&gt; it's pull, not push. You see new posts when you open the reader, not the moment they go live. If "the moment it happens" matters — job alerts, deal alerts, time-sensitive analysis — keep reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Poll the feed with a script (20 lines of Python)
&lt;/h2&gt;

&lt;p&gt;If you want actual notifications (Slack, Discord, email, SMS), poll the feed on a schedule and diff against what you've already seen. Here's the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;FEED_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.substack.com/feed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SEEN_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seen_ids.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SEEN_FILE&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SEEN_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FEED_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;new_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NEW: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# send_to_slack(f"New post: {post.title}\n{post.link}")
&lt;/span&gt;
&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SEEN_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on a cron every 15 minutes. The &lt;code&gt;seen_ids.json&lt;/code&gt; file is the entire "database" — and it's also the entire point. &lt;strong&gt;Dedup is the whole product.&lt;/strong&gt; Without remembering what you've already seen, every poll re-alerts on the same posts. With it, you get exactly one alert per new post.&lt;/p&gt;

&lt;p&gt;This works fine for one or two publications on a machine that's always on (a Raspberry Pi, a cheap VPS, or just your laptop if you don't mind it sleeping through alerts).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downsides at scale:&lt;/strong&gt; run it for ten publications and you're managing ten cron jobs and ten state files. Move machines and the state file doesn't come with you — hello, duplicate alerts for everything. And if the script crashes between "detect" and "save state," you either miss posts or double-alert. I learned all of this the annoying way, which is why method 3 exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: A scheduled monitor that handles the boring parts (no infrastructure)
&lt;/h2&gt;

&lt;p&gt;This is what I ended up building: a small Apify actor that watches a Substack publication and reports new posts. You give it the publication URL and a schedule. New posts land in the output dataset, which you can wire to Slack, email, or webhooks through Apify's integrations — no cron jobs or state files to babysit. A check that finds nothing new costs a fraction of a cent.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/delta_tools/substack-new-post-monitor?utm_source=dev.to&amp;amp;utm_medium=article&amp;amp;utm_campaign=substack-alerts-tutorial"&gt;Substack New-Post Monitor&lt;/a&gt; is the one I run myself. One job per tool, priced per result — a scheduled check that finds nothing new costs almost nothing.&lt;/p&gt;

&lt;p&gt;I'm not going to pretend it's the only option. If you like running your own infra, method 2 is genuinely fine and the code above is yours to keep. The actor exists for people who'd rather not babysit cron jobs and state files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which method should you pick?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Just want to read new posts in one place?&lt;/strong&gt; Method 1. RSS reader, done in two minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want push alerts and don't mind a tiny bit of ops?&lt;/strong&gt; Method 2. The script above, a cron job, somewhere always-on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want alerts with zero infrastructure?&lt;/strong&gt; Method 3. Set it once, get pinged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this work for paid Substack publications?&lt;/strong&gt;&lt;br&gt;
The public &lt;code&gt;/feed&lt;/code&gt; only includes free posts. Paid-only content behind the paywall isn't in the public feed — for that you'd need to be subscribed and logged in, which is a different (harder) problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How fast are the alerts?&lt;/strong&gt;&lt;br&gt;
Method 1 is whenever you open your reader. Method 2 is your cron interval (15 minutes is polite; don't hammer it every 60 seconds). Method 3 runs on whatever schedule you set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will Substack block polling?&lt;/strong&gt;&lt;br&gt;
At reasonable intervals (every 10–15 minutes), no. It's a standard RSS feed designed to be polled. Don't poll every minute from five IPs — that's how you get rate-limited anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I watch multiple publications at once?&lt;/strong&gt;&lt;br&gt;
Method 1: add multiple feeds to the reader. Method 2: loop over a list of feed URLs (one state file per publication, or namespace the IDs). Method 3: the actor's input takes a list of publications in a single run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about Substack's own email notifications?&lt;/strong&gt;&lt;br&gt;
They work, but they're all-or-nothing per publication and they land in your inbox mixed with everything else. These methods give you routing control — Slack, Discord, webhook, SMS — and let you watch publications without subscribing to their emails.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rss</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
