# Substack Newsletter Monitor - New Posts Tracker (`technicaldost/substack-newsletter-monitor`) Actor

New posts from any Substack publication as structured data - title, author, date, word count, reactions, comments and restacks - with no repeats between runs. Watch hundreds of newsletters on one schedule for a fraction of a cent each. No browser or API key needed.

- **URL**: https://apify.com/technicaldost/substack-newsletter-monitor.md
- **Developed by:** [Technical Dost Solutions](https://apify.com/technicaldost) (community)
- **Categories:** News, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Substack Newsletter Monitor — New Posts Tracker

**Follow any Substack publication and get its new posts as structured data — title, author, date, word count, reactions, comments and restacks — with no repeats.**

Point it at one publication or two hundred, put it on a schedule, and each run returns only posts that appeared since the last one. Substack's own email is fine for reading; this is for pipelines, dashboards, competitor tracking and content research.

Reads Substack's **public archive endpoint**. No browser, no login, no API key — which is why it is one of the cheapest ways to run this kind of monitoring.

***

### What you get

```json
{
  "title": "The Pulse: Meta's self-inflicted resignation-wave",
  "postUrl": "https://newsletter.pragmaticengineer.com/p/the-pulse-metas-self-inflicted-resignation",
  "postDate": "2026-08-14T16:55:55.370Z",
  "authors": ["Gergely Orosz"],
  "publicationUrl": "https://newsletter.pragmaticengineer.com",
  "subtitle": "The social media giant is offering $1M+ retainer equity grants to staff who are leaving...",
  "type": "newsletter",
  "audience": "only_paid",
  "isPaid": true,
  "wordCount": 3517,
  "reactionCount": 77,
  "commentCount": 12,
  "restackCount": 4,
  "reactions": { "❤": 77 },
  "coverImage": "https://substackcdn.com/image/fetch/...",
  "podcastUrl": null,
  "podcastDurationSeconds": null,
  "sectionName": null,
  "slug": "the-pulse-metas-self-inflicted-resignation",
  "postId": 211202149,
  "authorHandles": ["pragmaticengineer"],
  "scrapedAt": "2026-08-17T17:30:12.004Z"
}
```

`isPaid` is derived from Substack's own `audience` field, so you can separate public posts from subscriber-only ones without guessing. `reactionCount` sums the emoji map into one comparable number while `reactions` keeps the original breakdown.

***

### Main use cases

- **Competitor and industry tracking.** Watch every newsletter in your space and see what they publish, how long it is, and how much engagement it gets.
- **Content research.** Rank posts by reactions or comments to find what resonates in a niche.
- **Newsletter aggregation.** Feed several publications into one digest, a Slack channel or a website.
- **Free-vs-paid analysis.** See which topics a publication puts behind its paywall.
- **Author tracking.** Follow specific writers across multi-author publications.
- **LLM and RAG pipelines.** A clean, deduplicated feed of post metadata and URLs to fetch.

***

### Quick start

Monitor two publications for new posts:

```json
{
  "publications": ["https://newsletter.pragmaticengineer.com", "platformer"],
  "onlyNew": true,
  "maxPostsPerPublication": 20
}
```

Public posts only, from this year:

```json
{
  "publications": ["platformer"],
  "audience": "free",
  "since": "2026-01-01",
  "onlyNew": false
}
```

#### Publication input is forgiving

All of these resolve to the same publication:

| You paste | Resolves to |
|---|---|
| `https://newsletter.pragmaticengineer.com` | `https://newsletter.pragmaticengineer.com` |
| `newsletter.pragmaticengineer.com` | `https://newsletter.pragmaticengineer.com` |
| `.../p/some-post-slug` | `https://newsletter.pragmaticengineer.com` |
| `platformer` | `https://platformer.substack.com` |

Custom domains work too — many established Substacks do not use a `substack.com` address.

***

### Input reference

| Field | Type | Default | What it does |
|---|---|---|---|
| `publications` | array | **required** | URLs, custom domains or bare handles. Up to 200. |
| `onlyNew` | boolean | `true` | Skip posts returned by an earlier run. |
| `maxPostsPerPublication` | integer | `20` | Newest-first cap per publication. |
| `maxPosts` | integer | `500` | Hard cap on posts returned **and charged** this run. |
| `audience` | select | `any` | `any`, `free`, or `paid`. |
| `since` | string | — | `YYYY-MM-DD`. Stops paging back at this date. |
| `stateStoreName` | string | `substack-monitor-state` | Named store holding the "already seen" list. |

**Use `since` on large publications.** Without it, a high `maxPostsPerPublication` will page back through years of archive. With it, the Actor stops as soon as it reaches an older post, which keeps scheduled runs fast.

***

### Running it on a schedule

1. Add your publications, save as a **Task**.
2. Add a daily **Schedule** on that Task.
3. Leave `onlyNew` on.

The "already seen" list is keyed on Substack's own post ID and lives in a named Key-Value Store **on your own account**, so your history is private and persists between runs. Up to 50,000 post IDs are remembered.

Add a webhook to push new posts straight into Slack, Notion or a database.

***

### Pricing

| Event | Price | When it happens |
|---|---|---|
| **Post** | **$0.0015** | One post returned to you |
| Actor start | $0.001 | Once per run |

**You are not charged for:**

- posts you were already sent in an earlier run (with `onlyNew` on)
- posts filtered out by the `audience` setting
- publications that fail or are unreachable
- invalid publication URLs — reported and skipped before any work
- duplicate publications in your list
- anything after the maximum charge you set for the run

**Worked example.** A daily run over 20 publications producing ~15 new posts a day costs about `(15 × $0.0015) + $0.001 = $0.0235` per run, roughly **$0.71/month**.

#### How this compares

| Actor | Price per post | Per run | New-only monitoring |
|---|---|---|---|
| **This Actor** | **$0.0015** | $0.001 | yes |
| `easyapi/substack-posts-scraper` | $0.00499 | **$0.09** | — |
| `cryptosignals/substack-scraper` | $0.005 | — | — |
| `memo23/substack-scraper` | $0.003 | $0.005 | — |
| `easyapi/substack-leaderboard-scraper` | $0.00299 | $0.09 | — |
| `sourabhbgp/substack-scraper` | $0.0003 | $0.00005 | — |

Competitors' list prices as published on Apify Store on 2026-08-17; check current figures before relying on this table.

**Be aware `sourabhbgp/substack-scraper` is cheaper per post.** If you want a bulk one-off dump and nothing else, it will cost you less. This Actor is priced for the recurring case: the per-run fee is 1/90th of the two easyapi actors', and `onlyNew` means a daily schedule charges for a handful of new posts rather than re-charging for the whole archive every run. Over a month of daily runs that is usually the cheaper outcome — but compare against your own volumes.

***

### Using the API

```bash
curl -X POST "https://api.apify.com/v2/acts/technicaldost~substack-newsletter-monitor/run-sync-get-dataset-items?token=<TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{ "publications": ["platformer"], "maxPostsPerPublication": 10, "onlyNew": false }'
```

**JavaScript — build a digest of new free posts:**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('technicaldost/substack-newsletter-monitor').call({
    publications: ['platformer', 'https://newsletter.pragmaticengineer.com'],
    audience: 'free',
    onlyNew: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const p of items.sort((a, b) => (b.reactionCount ?? 0) - (a.reactionCount ?? 0))) {
    console.log(`${p.reactionCount ?? 0} ❤  ${p.title} — ${p.postUrl}`);
}
```

**Python:**

```python
from apify_client import ApifyClient

client = ApifyClient("<TOKEN>")

run = client.actor("technicaldost/substack-newsletter-monitor").call(run_input={
    "publications": ["platformer"],
    "onlyNew": True,
})

for p in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(p["postDate"], p["title"], p["wordCount"], p["reactionCount"])
```

***

### Limitations

Stated plainly so there are no surprises:

- **Metadata, not article bodies.** The Actor returns each post's title, subtitle, description and public engagement counts plus its URL. It does not return the full article text. **Paywalled content is never fetched** — `isPaid` tells you a post is subscriber-only, and that is where it stops.
- **No subscriber counts.** Substack does not publish them, and this Actor does not estimate or invent them. Any tool claiming a subscriber number for an arbitrary publication is guessing.
- **Engagement counts are a snapshot** taken at `scrapedAt`. Reactions and comments keep accruing after a post is published, so a post captured an hour after publication will show lower numbers than the same post captured a week later. Re-run with `onlyNew: false` if you want refreshed counts.
- **No discovery or search.** You supply the publications; the Actor does not find newsletters by topic or keyword.
- **Substack publications only.** Not Beehiiv, Ghost or Mailchimp.
- **`restackCount` and some fields are absent on older posts**, because Substack added them later. Missing values are `null`, never zero.

***

### Related Actors

- **[RSS Feed Scraper](https://apify.com/technicaldost/rss-feed-scraper)** — if you also follow blogs, news sites and podcasts, an RSS feed reader covers everything that publishes a feed. ★5.0
- **[SEC EDGAR Filing Monitor](https://apify.com/technicaldost/sec-edgar-filing-monitor)** — the same new-only monitoring pattern for US public company filings.
- **[UK New Company Leads Monitor](https://apify.com/technicaldost/uk-new-company-leads-monitor)** — daily feed of newly incorporated UK companies.

***

### Data source and responsible use

Data comes from each publication's **public archive endpoint** — the same listing any visitor sees on the publication's archive page. No login, no cookies, no paywall circumvention. Requests are paced so that monitoring many publications stays courteous.

Post text remains the copyright of its author. Use the metadata and links for research, monitoring and aggregation; do not republish authors' content without permission.

Not affiliated with or endorsed by Substack.

# Actor input Schema

## `publications` (type: `array`):

Publication URLs, custom domains or bare handles. All of these work: https://platformer.news, foo.substack.com, platformer, or even a link to a single post — the publication is worked out from it.

## `onlyNew` (type: `boolean`):

Recommended for scheduled runs. The actor remembers every post it has returned, so a daily schedule only returns genuinely new posts — and you are only charged for those.

## `maxPostsPerPublication` (type: `integer`):

Newest-first cap for each publication. Keep it small for monitoring; raise it for a one-off archive pull.

## `maxPosts` (type: `integer`):

Hard cap on how many posts this run returns and charges for.

## `audience` (type: `string`):

Filter by whether a post is public or subscriber-only.

## `since` (type: `string`):

Stops paging back through the archive once it reaches this date, which keeps scheduled runs fast on publications with years of history.

## `stateStoreName` (type: `string`):

Named Key-Value Store on your account holding the list of posts already returned. Use different names to keep separate watchlists independent.

## Actor input object example

```json
{
  "publications": [
    "https://newsletter.pragmaticengineer.com"
  ],
  "onlyNew": true,
  "maxPostsPerPublication": 20,
  "maxPosts": 500,
  "audience": "any",
  "stateStoreName": "substack-monitor-state"
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `engagement` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "publications": [
        "https://newsletter.pragmaticengineer.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("technicaldost/substack-newsletter-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "publications": ["https://newsletter.pragmaticengineer.com"] }

# Run the Actor and wait for it to finish
run = client.actor("technicaldost/substack-newsletter-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "publications": [
    "https://newsletter.pragmaticengineer.com"
  ]
}' |
apify call technicaldost/substack-newsletter-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,technicaldost/substack-newsletter-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/fssiJIZXHxfKTSVNf/builds/HP3xnfEDzrXU8Kraa/openapi.json
