# Dataset New Items Alerts: Email, Slack, Discord & Telegram (`genesis.labs.co/dataset-new-items-alerts`) Actor

Get an email, Slack, Discord, Telegram or webhook alert with only the new items from any Apify scraper's dataset. Remembers what was sent, filters with simple rules (price < 500), one digest per run. Failed sends are free.

- **URL**: https://apify.com/genesis.labs.co/dataset-new-items-alerts.md
- **Developed by:** [Nicholas Powers](https://apify.com/genesis.labs.co) (community)
- **Categories:** Integrations, Automation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 alert delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Dataset New Items Alerts: Email, Slack, Discord & Telegram

**Get an alert with only the new items every time your Apify scraper runs.** Connect this Actor to any scraper or task, and after each run it checks the dataset, remembers what it already sent, and posts **one tidy digest of the new items** to **Slack, Discord, Telegram, a webhook (Zapier, Make, n8n, your API) or email**. New jobs, new listings, price drops, new products, new articles, new reviews: you hear about them once, and only once.

Add **alert rules** like `price < 500`, `title contains remote` or `rating >= 4.5` so you only get pinged for items that matter. **Failed deliveries are free**, runs with nothing new send nothing and cost almost nothing, and the Actor never goes over your max cost per run.

### What it does

- 🆕 **Only new items**: remembers the items it has already alerted on (by `url`, or any key fields you choose) across runs, so a scheduled scraper never repeats itself. URLs are matched without tracking parameters (`utm_*`, `fbclid`...), `www.` or trailing slashes; text is matched case-insensitively.
- 🎯 **Alert rules**: `=`, `!=`, `>`, `>=`, `<`, `<=`, `contains`, `!contains`, `matches` (regex), `exists`, `empty`. Numbers like `$1,299` and dates like `2026-11-01` are compared as numbers and dates. Dot paths (`offer.price`) work. Combine with ALL or ANY.
- 📣 **Channels**: Slack (incoming webhook, Block Kit), Discord (webhook embeds with optional thumbnails), Telegram (your bot), any webhook (full items as JSON), and email to your own Apify account address. Use several at once.
- 🧾 **One digest per run**, not one message per item: a title like *"12 new items"*, each item's headline linked to its URL, the fields you choose (price, location, date...), and "+N more" with a link to the full dataset. Titles, links and fields are **detected automatically** if you don't set them.
- 🔌 **Built for integrations**: set **Dataset** to `{{resource.defaultDatasetId}}` in *"Run another Actor when a run succeeds"* and it works for every future run. Runs with **limited permissions** (it can read only the dataset you give it).
- 🛟 **Reliable**: retries 429 rate limits (honoring `Retry-After`), 5xx errors and timeouts; splits long digests to fit Slack (50 blocks), Discord (10 embeds / 6,000 chars) and Telegram (4,096 chars) limits; stable error codes per channel. If every channel fails, the items are **not** marked as sent, so the next run tries again.
- 🔒 **Secrets stay secret**: webhook URLs and bot tokens are encrypted input fields and never written to logs.
- 🧪 **Dry run**: preview every message in the dataset without sending, charging or touching the memory.

### Pricing

Pay per event. Compute and storage are included.

| Event | Free & Bronze | Silver | Gold & above |
|---|---|---|---|
| Alert delivered (one digest to one channel, any number of items) | **$0.0015** | $0.0012 | $0.001 |
| 1,000 items checked, beyond the first 1,000 in a run | $0.001 | $0.001 | $0.001 |

Plus Apify's standard Actor start event ($0.00005 per run at the default 256 MB).

**Not charged:** failed deliveries, runs with nothing new (unless you turn on the heartbeat), the first 1,000 items of every run, dry runs, the free demo, and items checked in runs where every channel failed.

**Examples (Free plan prices)**

| Scenario | Cost |
|---|---|
| Hourly job scraper (200 items per run) → Slack, about 3 runs/day have new jobs | 90 alerts × $0.0015 + 720 starts × $0.00005 = **about $0.17/month** |
| Daily real-estate scraper (5,000 listings) → Telegram + Discord, every day has new listings | 30 × (2 × $0.0015 + 4 × $0.001) = **about $0.21/month** |
| One run, 50,000 items checked, 1 webhook | $0.0015 + 49 × $0.001 = **$0.05** |
| Run with nothing new (up to 1,000 items checked) | **$0.00005** (Actor start only) |

**Email note:** email is sent through Apify's free [Send Email](https://apify.com/apify/send-mail) Actor, which delivers **only to the email address of your Apify account** and runs as a short separate run in your account (its small compute usage is billed by Apify as usual, typically a fraction of a cent). To email other people, point the webhook at Zapier, Make or n8n.

### How to use it

#### 1. Alerts after every scraper run (recommended)

1. Open your scraper or saved task → **Integrations** → **Run another Actor when a run succeeds** → choose **Dataset New Items Alerts**.
2. In its input, set **Dataset** to `{{resource.defaultDatasetId}}` and fill in a channel (for example the Slack webhook URL).
3. Done. Every successful run of the scraper triggers a check; you get a message only when there are new items.

```json
{
  "datasetId": "{{resource.defaultDatasetId}}",
  "keyFields": ["url"],
  "slackWebhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
  "fields": ["price", "location"],
  "title": "{{count}} new listings"
}
```

Tip: if the scraper already has hundreds of items, set **First run** to *Silent*: the first run just remembers everything and later runs alert only on genuinely new items.

#### 2. Only alert on what matters

```json
{
  "datasetId": "{{resource.defaultDatasetId}}",
  "filters": ["price <= 1500", "city = Tempe", "title !contains studio"],
  "telegramBotToken": "123456789:AA...",
  "telegramChatId": "-1001234567890",
  "fields": ["price", "bedrooms", "city"]
}
```

#### 3. Send new items to your own system

The webhook receives one JSON POST per run:

```json
{
  "event": "dataset.new_items",
  "title": "3 new items",
  "newItemCount": 3,
  "items": [{ "title": "...", "url": "https://...", "price": 129 }],
  "itemsTruncated": false,
  "datasetId": "WkzbQMuFYuamGv3YF",
  "sourceActorId": "...",
  "sourceRunId": "...",
  "memoryName": "...",
  "datasetUrl": "https://console.apify.com/storage/datasets/WkzbQMuFYuamGv3YF",
  "sentAt": "2026-11-01T17:13:09.000Z"
}
```

Add headers such as `{"Authorization": "Bearer ..."}` in **Webhook headers**. Zapier "Catch Hook", Make "Custom webhook" and n8n "Webhook" nodes accept it as is.

#### Getting the webhook URLs

- **Slack**: create an app at api.slack.com/apps → *Incoming Webhooks* → *Add New Webhook to Workspace* → copy the `https://hooks.slack.com/services/...` URL.
- **Discord**: channel settings → *Integrations* → *Webhooks* → *New Webhook* → *Copy Webhook URL*.
- **Telegram**: message @BotFather → `/newbot` → copy the token. Add the bot to your group or channel (or send it `/start`), then use the chat ID (e.g. `-1001234567890`) or `@channelname`.

#### How "new" is decided

Each item gets a key from **Key fields** (default `url`). Keys of alerted items are stored as compact fingerprints in a key-value store in your account named `alerts-memory-<memory name>`. Leave **Memory name** empty and one is created per source Actor + channels + filters, so two different alert setups never share memory. Set a name to share it on purpose, or **Reset memory** to start over. Items matching your rules are remembered only after at least one channel received them.

### Output

- **Dataset**: a delivery log, one row per channel: `channel`, `status` (`sent`, `partial`, `failed`, `skipped`, `dry-run`), `messagesSent`, `newItemCount`, `itemsShown`, `errorCode`, `error`, `charged`. In dry runs each row also has a `preview` of the exact message.
- **`NEW_ITEMS`** record: the new matching items of this run (first 1,000), ready for the next step of a pipeline.
- **`OUTPUT`** record: summary with items checked, new, filtered out, seen in earlier runs, duplicates, memory size and per-channel status.

### Error codes

| Code | Meaning | Charged |
|---|---|---|
| `INPUT_INVALID` | Input problem (e.g. unfilled `{{resource.defaultDatasetId}}`, bad filter); the message says what to fix | No |
| `DATASET_NOT_FOUND` | Wrong or expired dataset ID | No |
| `DATASET_NO_ACCESS` | The dataset was not passed in the **Dataset** field (limited permissions) | No |
| `WEBHOOK_INVALID` | Slack/Discord/your webhook rejected the URL (404, 410, 401...), or Telegram rejected the bot token | No |
| `CHAT_NOT_FOUND` | Telegram chat ID wrong, or the bot isn't in the chat | No |
| `MESSAGE_REJECTED` | The service refused the message content (HTTP 400) | No |
| `RATE_LIMITED` | Still HTTP 429 after 4 attempts | No |
| `SERVICE_ERROR` | HTTP 5xx after 4 attempts | No |
| `NETWORK_ERROR` | DNS, TLS or timeout after 4 attempts | No |
| `EMAIL_NOT_OWN_ADDRESS` | Send Email delivers only to your Apify account's email | No |
| `EMAIL_FAILED` | The Send Email run failed | No |
| `MAX_COST_REACHED` | Your max cost per run was reached; channel skipped | No |

If some channels succeed and others fail, the run succeeds and the failures are listed in the log. If **every** channel fails, the run fails (so your integration shows it), nothing is charged, and the items stay "new" for the next run.

### FAQ

**Which scrapers does it work with?** Any Actor or task that writes to a dataset: job boards, real-estate, e-commerce, news, reviews, social media, your own Actors.

**Is one message sent per item?** No, one digest per channel per run (split into a few messages only when Slack/Discord/Telegram size limits require it). You pay per digest, not per item.

**Can I get a message even when nothing is new?** Yes, turn on *Send a message when nothing is new* for a daily "No new items" heartbeat (charged as a normal alert).

**What about very large datasets?** It streams the dataset in pages and stores fingerprints compactly; millions of items work. Use *Max items to check* or your max cost per run to cap it.

**Is my data shared?** The Actor sends items only to the channels you configure. It is not affiliated with Slack, Discord, Telegram or any scraper you connect it to.

# Actor input Schema

## `datasetId` (type: `string`):

The dataset to watch. In an integration ("Run another Actor when a run succeeds" on your scraper or task) set this to {{resource.defaultDatasetId}} so every finished run is checked.

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

Alert only on items never sent before (remembered across runs). Off: every matching item in the dataset is sent each run.

## `keyFields` (type: `array`):

Fields that identify an item, e.g. url, or title + company. Dot paths work (offer.id). URLs are compared without tracking parameters, www or trailing slashes; text is compared case-insensitively.

## `memoryName` (type: `string`):

Optional. Runs with the same memory name share the list of already-sent items (stored in a key-value store in your account). Empty: one memory per source Actor + channels + filters, created automatically.

## `firstRun` (type: `string`):

What to do when the memory is new. Silent is handy when a scraper already has hundreds of items and you only want future ones.

## `resetMemory` (type: `boolean`):

Forget all remembered items for this memory and treat everything as new.

## `filters` (type: `array`):

Send only items that match, one rule per line: "price < 500", "title contains remote", "title !contains senior", "rating >= 4.5", "postedAt >= 2026-11-01", "url matches /jobs/", "email exists". Operators: = != > >= < <= contains !contains matches exists empty.

## `filterMode` (type: `string`):

How multiple rules combine.

## `emailTo` (type: `array`):

The email address of YOUR Apify account (Apify's free apify/send-mail Actor, which this uses, delivers only to the account owner; no SMTP needed). To email other people, send the webhook to Zapier, Make or n8n.

## `slackWebhookUrl` (type: `string`):

Incoming webhook from your Slack app (https://hooks.slack.com/services/...). Stored encrypted.

## `discordWebhookUrl` (type: `string`):

Server Settings > Integrations > Webhooks > Copy Webhook URL. Stored encrypted.

## `telegramBotToken` (type: `string`):

Token of your bot from @BotFather (123456789:AA...). Stored encrypted.

## `telegramChatId` (type: `string`):

Chat, group or channel to post to: a number like -1001234567890, or @channelusername. The bot must be a member (or you must have sent it /start).

## `webhookUrl` (type: `string`):

Receives one JSON POST per run with the new items (event, newItemCount, items, datasetId, sourceRunId...).

## `webhookHeaders` (type: `object`):

Optional extra HTTP headers for the webhook, e.g. {"Authorization": "Bearer ..."}.

## `title` (type: `string`):

Email subject and message heading. Variables: {{count}}, {{date}}, {{datasetId}}, {{memoryName}}, {{checked}}.

## `intro` (type: `string`):

Optional line under the title, e.g. "New apartments under $1,500 in Tempe". Same variables as the title.

## `titleField` (type: `string`):

Field used as each item's headline. Empty: detected automatically (title, name, headline...).

## `linkField` (type: `string`):

Field with the item's URL, used as the link. Empty: detected automatically (url, link...).

## `fields` (type: `array`):

Up to 12 extra fields shown under each item, e.g. price, location, postedAt. Empty: up to 4 short fields are picked automatically.

## `imageField` (type: `string`):

Optional field with an image URL, shown as a thumbnail in Discord.

## `maxItemsInMessage` (type: `integer`):

Items listed in email, Slack, Discord and Telegram. The rest are counted ("+N more") with a link to the dataset.

## `maxItemsInWebhook` (type: `integer`):

Full items included in the webhook JSON.

## `sendWhenNothingNew` (type: `boolean`):

Off: no message (and no charge) when there are no new items. On: send a short "No new items" heartbeat.

## `discordUsername` (type: `string`):

Optional name shown as the sender in Discord.

## `dryRun` (type: `boolean`):

Build the messages and save previews in the dataset without sending, charging or updating memory.

## `maxItemsToCheck` (type: `integer`):

Safety limit on items read from the dataset per run.

## Actor input object example

```json
{
  "onlyNew": true,
  "keyFields": [
    "url"
  ],
  "firstRun": "notify",
  "resetMemory": false,
  "filterMode": "all",
  "title": "{{count}} new items",
  "maxItemsInMessage": 20,
  "maxItemsInWebhook": 100,
  "sendWhenNothingNew": false,
  "dryRun": false,
  "maxItemsToCheck": 1000000
}
```

# Actor output Schema

## `deliveries` (type: `string`):

One row per channel: status, messages sent, error code.

## `newItems` (type: `string`):

The new matching items found in this run (first 1,000).

## `summary` (type: `string`):

Items checked, new, filtered, seen before; per-channel status and error codes.

# 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 = {
    "keyFields": [
        "url"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("genesis.labs.co/dataset-new-items-alerts").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 = { "keyFields": ["url"] }

# Run the Actor and wait for it to finish
run = client.actor("genesis.labs.co/dataset-new-items-alerts").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 '{
  "keyFields": [
    "url"
  ]
}' |
apify call genesis.labs.co/dataset-new-items-alerts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,genesis.labs.co/dataset-new-items-alerts"
        }
    }
}
```

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/GmwHqgrjYgkx6NTvK/builds/vcamB0VQf10nI7Lft/openapi.json
