# Instantly Lead Pusher — Push Any Apify Dataset to Campaigns (`programmx/instantly-lead-pusher`) Actor

Push any Apify dataset into an Instantly.ai campaign or lead list — fields auto-mapped, every extra column becomes a {{custom\_variable}} merge tag. Dedup + dry run.

- **URL**: https://apify.com/programmx/instantly-lead-pusher.md
- **Developed by:** [Yasir Azeem](https://apify.com/programmx) (community)
- **Categories:** Integrations, Lead generation
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 lead pusheds

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/platform/actors/running/actors-in-store#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

## Instantly Lead Pusher — Push Any Apify Dataset into Instantly Campaigns

![How the Apify to Instantly integration works: point at any dataset, fields auto-mapped to merge tags, pushed into your campaign](https://raw.githubusercontent.com/PTechFusion/programmx-actor-assets/main/instantly-how-it-works.png)

Push leads from any Apify dataset straight into an Instantly.ai campaign or lead list — no CSV export, no manual import, no column fiddling. Point it at the dataset any scraper produced, and every lead lands in Instantly with its fields mapped and its extra columns ready to use in your email copy. **Your scraped columns become {{merge\_tags}} — not just name and email.**

### The CSV step this removes

The usual path from an Apify scraper to an Instantly campaign is: download the dataset as CSV, open it, rename columns to match Instantly's importer, re-upload, map fields by hand, repeat next week. This actor replaces that with one run — or zero runs, once you attach it to your scraper as an integration that fires automatically after every scrape.

### Every extra column becomes a personalization merge tag

Instantly supports custom variables per lead, and this actor is built around them. Standard fields (email, first name, last name, company, job title, phone, website) are auto-detected from your dataset's column names — any casing or format. **Every other column is sent as a custom variable**, so niche scraped data becomes personalization tokens in your sequences.

Example: a dataset of eBay UK business sellers with columns `businessName`, `email`, `storeName`, `feedbackScore`, `itemsForSale` gives you, in Instantly:

> Saw that {{store\_name}} has {{items\_for\_sale}} listings live and a feedback score of {{feedback\_score}} — most sellers at that volume are wrestling with…

That's copy no plain CSV import gives you without manual column mapping. A `personalization` column (or `icebreaker` / `firstLine`) maps to Instantly's personalization field automatically.

### Built-in safety: dedup, verification, dry run

- **Duplicate protection, three layers**: skip leads already in a campaign, already in a list, or anywhere in your workspace (Instantly-side flags, on by default where it makes re-runs safe), plus in-dataset email dedup on our side. Skipped and duplicate leads are never billed.
- **Email verification**: optionally trigger Instantly's verification on import (uses your Instantly verification credits).
- **Blocklist aware**: leads on your workspace blocklist (or a specific blocklist you name) are filtered by Instantly and never billed.
- **Dry run — on by default for your first run**: validates your API key and target, reads the dataset, resolves the full field mapping and every merge tag, and reports exactly what *would* be pushed. Nothing is sent, nothing is billed. Turn it off when the preview looks right.

### Attach it to any scraper as an integration

On any other actor's page in Apify Console, open the **Integrations** tab and add this actor as a "run finishes" integration. The finished run's dataset is passed in automatically — you only set your API key and the target campaign or list once. Every future scrape then flows into Instantly on its own.

> **Recipe — a self-filling campaign:** schedule a lead scraper in its monitor/new-results mode (weekly), attach this actor as its integration with `skipIfInCampaign` on. New leads found → pushed → sequenced, hands off.

### How to use

1. In Instantly: Settings → Integrations → API keys → create a key (any paid Instantly plan includes API access).
2. Paste the key into **Instantly API key** (stored encrypted by Apify, never logged).
3. Pick the target: a **campaign** (leads start receiving its sequence) or a **lead list** (stored for review first). Paste the UUID from the Instantly URL — or have the actor find/create a list by name.
4. Point **Source dataset ID** at any dataset in your account, or leave it empty when running as an integration.
5. Run once with **Dry run** on (the default). Check the mapping table and merge-tag list in the run's OUTPUT.
6. Turn dry run off and push. From then on, attach it as an integration and forget it.

#### Input example

Copy this into the **JSON** tab of the input editor. Dry run is on, so this validates and previews without sending or billing anything:

```json
{
  "instantlyApiKey": "YOUR_INSTANTLY_API_KEY",
  "datasetId": "jzBc4FybmLuK4QC8Y",
  "targetType": "campaign",
  "campaignId": "00000000-0000-0000-0000-000000000000",
  "dryRun": true,
  "dedupeByEmail": true,
  "skipIfInCampaign": true,
  "maxLeads": 100
}
```

The `datasetId` above is a public 5-row sample dataset, so you can run the preview before you own any scraped data. Swap in your own dataset ID and campaign UUID, check the mapping table in the run's `OUTPUT`, then set `dryRun` to `false`.

Pushing into a lead list instead of a live campaign — created by name if it does not exist yet:

```json
{
  "instantlyApiKey": "YOUR_INSTANTLY_API_KEY",
  "datasetId": "YOUR_DATASET_ID",
  "targetType": "list",
  "createListIfMissing": true,
  "newListName": "Apify — new sellers, week 32",
  "dryRun": false
}
```

### Pricing: $0.002 per lead pushed — $2 per 1,000

You pay only for leads Instantly actually accepts — `leads_uploaded` from Instantly's own response. Rows without an email, in-dataset duplicates, leads Instantly skips as already known, invalid emails, and blocklist matches are **never billed**. Dry runs are free. There is nothing else on the meter: no per-row fee, no platform-cost pass-through surprises.

For comparison: the manual route is a CSV export, a column-renaming session, and an import wizard — per scraper, per week. This is priced to make never doing that again a non-decision.

### Output example

Run summary (key-value store `OUTPUT`):

```json
{
    "dryRun": false,
    "target": { "type": "campaign", "id": "0197…", "name": "UK resellers — March" },
    "rowsRead": 1240,
    "pushed": 1093,
    "skippedAlreadyKnown": 88,
    "inPayloadDuplicates": 31,
    "droppedNoEmail": 24,
    "invalidEmailsServer": 4,
    "inBlocklist": 0,
    "failedLeads": 0,
    "remainingInPlan": 8907,
    "mapping": {
        "email": { "column": "email", "source": "auto" },
        "company_name": { "column": "businessName", "source": "auto" }
    },
    "customVariableKeys": ["store_name", "feedback_score", "items_for_sale", "source_domain"]
}
```

Plus one dataset row per lead with a definite outcome — `pushed` (with its Instantly lead ID), `dropped` (with the reason), or `failed` — so you can audit or retry precisely.

### FAQ

#### How do I import Apify results into Instantly automatically?

Attach this actor to your scraper via the Integrations tab ("when run finishes"). Every finished scrape pushes its results into your chosen campaign or list with no manual step. Manual runs work too — paste any dataset ID.

#### What exactly am I billed for?

One event per lead Instantly reports as uploaded, at $0.002 ($2 per 1,000 leads). The number comes from Instantly's API response, not from our row count — anything skipped, duplicate, invalid, or blocklisted costs nothing. Dry runs cost nothing.

#### Does it deduplicate leads?

Three ways: Instantly-side skip flags (already in a campaign / a list / anywhere in the workspace), plus same-email rows inside your dataset are collapsed to one before sending. Re-running the same push is safe by default.

#### Can I use my scraped data in the email copy?

Yes — that's the point. Every non-standard column becomes an Instantly custom variable ({{merge\_tag}}). Column names are converted to snake\_case tags (`feedbackScore` → `{{feedback_score}}`). The dry run lists every tag you'll have.

#### Does it work with datasets from any actor?

Any dataset with one lead per row. Standard fields are auto-detected across common naming styles (email/Email/e\_mail, firstName/first\_name, company/businessName…); anything the auto-detection gets wrong you can pin with the `fieldMapping` input. A campaign push needs an email per row; a lead list also accepts rows with just a name.

#### What happens when my Instantly plan's upload limit is reached?

Instantly enforces a monthly uploaded-contacts cap per plan. The run summary surfaces `remainingInPlan` after every push and warns loudly when it hits zero — beyond it, Instantly silently skips further uploads until you upgrade or the month resets.

#### Is my Instantly API key safe?

The key is a secret input: Apify stores it encrypted, and this actor never writes it to the log — error messages are scrubbed before they're reported. It is sent only to api.instantly.ai.

### Ready-made lead sources

This actor takes a dataset from *any* scraper, but these three are built by the same team and hand off to it cleanly — attach this actor on their **Integrations** tab and the pipeline runs itself:

| Source actor | What lands in Instantly |
|---|---|
| **[eBay Business Leads Scraper](https://apify.com/programmx/ebay-business-leads)** | One verified business seller per row — email, phone, company and VAT number. `storeName`, `feedbackScore` and `itemsForSale` arrive as `{{merge_tags}}`, which is what makes an opener like *"saw {{store\_name}} has {{items\_for\_sale}} listings live"* possible. |
| **[Property Finder Scraper + AI Deal Score](https://apify.com/programmx/propertyfinder-deal-scraper)** | UAE property listings with the agent's email, phone and WhatsApp in every row, plus `dealScoreBand` and `community` as merge tags. |
| **[German Estate Agent Leads](https://apify.com/programmx/immoscout24-agent-leads)** | Verified contact records for German estate agencies. **Push these to a lead list, not a live campaign** — German UWG §7 prohibits unsolicited commercial email, including B2B, so these records are for CRM enrichment, phone outreach and consented nurture. |

> Whatever the source, you are responsible for having a lawful basis to email the people in your dataset. This actor moves data you already chose to collect; it does not make cold outreach lawful where it is not.

#### Why were some rows not pushed?

The run log and OUTPUT name every category with its count and the input that changes it: rows with no email (map the right column via `fieldMapping`), duplicates (turn off `dedupeByEmail` to send anyway), leads Instantly already knows (skip flags), invalid emails, blocklist matches. Nothing is dropped silently, and dropped rows are never billed.

***

*ProgrammX · programmx.com · hello@programmx.com*

# Actor input Schema

## `instantlyApiKey` (type: `string`):

Create one in Instantly → Settings → Integrations → API keys (any paid Instantly plan includes API access; scope `leads:create` or broader). Stored encrypted by Apify and never written to the log. The key stays in your Apify account — this actor only sends it to api.instantly.ai. Leave it empty on a dry run and you get a free offline preview: the actor maps your dataset and shows every field and {{merge\_tag}} each lead would receive, without contacting Instantly at all.

## `targetType` (type: `string`):

Where the leads go. A campaign needs every lead to have an email address; a lead list also accepts rows that only have a name. If unsure, push into a lead list first and review before attaching to a campaign.

## `campaignId` (type: `string`):

The campaign's UUID — copy it from the campaign's URL: app.instantly.ai/app/campaign/<this part>. Only used when 'Push into' is Campaign.

## `listId` (type: `string`):

The lead list's UUID. Only used when 'Push into' is Lead list. Leave empty and use 'Create list if missing' below to have the actor find or create a list by name instead.

## `createListIfMissing` (type: `boolean`):

Lead-list target only. The actor first searches your workspace for a list whose name exactly matches 'New list name' and reuses it; only if none exists is a new list created. Safe to leave on for scheduled runs — you will not get one new list per run.

## `newListName` (type: `string`):

Required when 'Create the lead list if it doesn't exist' is on and no list ID is given. Tip: name it after the source, e.g. 'eBay UK car parts sellers'.

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

The Apify dataset holding your leads — any dataset in your account works, whichever actor produced it. Leave empty when this actor is attached to another actor as an integration: the finished run's dataset is passed in automatically.

## `payload` (type: `object`):

Filled by Apify when this actor runs as an integration on another actor's run; carries that run's default dataset ID. Do not set it manually — use 'Source dataset ID' instead.

## `maxLeads` (type: `integer`):

Hard cap on dataset rows read, 0 = all. With a real run this caps what can be pushed and billed — start small (e.g. 100), check the result in Instantly, then raise it.

## `fieldMapping` (type: `object`):

Only needed when auto-detection gets a column wrong — the run log and dry-run preview show what was detected. Keys are Instantly fields (email, first\_name, last\_name, company\_name, job\_title, phone, website, personalization), values are your dataset's column names. Example: {"company\_name": "storeName", "website": "shopUrl"}. Your mapping always beats auto-detection.

## `customVariableColumns` (type: `array`):

Leave empty (recommended) to turn EVERY column that isn't a standard field into an Instantly custom variable, usable as a {{merge\_tag}} in your email copy. List specific column names here to send only those.

## `sendCustomVariables` (type: `boolean`):

Off = only the standard fields (name, email, company, …) are sent and your extra columns are ignored. Leave on to keep the personalization data — it is the point of pushing from a scraper instead of a CSV.

## `skipIfInWorkspace` (type: `boolean`):

Instantly skips any lead whose email already exists anywhere in your workspace — any campaign or list. Strictest duplicate guard; skipped leads are never billed by this actor.

## `skipIfInCampaign` (type: `boolean`):

On (recommended): a lead already present in any campaign is skipped, so nobody gets emailed twice and re-running this actor is always safe. Skipped leads are never billed.

## `skipIfInList` (type: `boolean`):

On (recommended): a lead already present in any lead list is skipped. Makes re-runs and scheduled pushes idempotent. Skipped leads are never billed.

## `dedupeByEmail` (type: `boolean`):

On (recommended): if the same email appears on several dataset rows, only the first row is pushed. Duplicates are dropped on our side — never sent to Instantly, never billed.

## `verifyLeadsOnImport` (type: `boolean`):

Asks Instantly to run its email verification on the imported leads. Uses your Instantly verification credits — billed by Instantly, not by this actor.

## `blocklistId` (type: `string`):

Optional. UUID of a specific Instantly blocklist to check leads against. Leave empty to use your workspace's default blocklist. Blocked leads are never billed.

## `batchSize` (type: `integer`):

Leads per API request (Instantly's maximum is 1,000). The default is fine for almost everyone; smaller batches shrink the blast radius of a single failed request on flaky networks.

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

Full rehearsal: checks your API key and target (read-only), reads the dataset, maps every field and custom variable, and reports exactly what WOULD be pushed — but sends nothing to Instantly and bills nothing. Recommended for your first run on any new dataset; turn it off to push for real. With no API key given, a dry run still previews the mapping — it just skips the Instantly checks.

## Actor input object example

```json
{
  "targetType": "campaign",
  "campaignId": "",
  "listId": "",
  "createListIfMissing": false,
  "newListName": "",
  "datasetId": "jzBc4FybmLuK4QC8Y",
  "maxLeads": 100,
  "fieldMapping": {},
  "customVariableColumns": [],
  "sendCustomVariables": true,
  "skipIfInWorkspace": false,
  "skipIfInCampaign": true,
  "skipIfInList": true,
  "dedupeByEmail": true,
  "verifyLeadsOnImport": false,
  "blocklistId": "",
  "batchSize": 500,
  "dryRun": true
}
```

# Actor output Schema

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

Totals from Instantly's own responses: pushed, duplicates, skipped, invalid emails, blocklisted, remaining plan capacity — plus the resolved field mapping and the custom-variable merge tags that are now available in your email copy. On a dry run: everything that WOULD have been pushed.

## `pushResults` (type: `string`):

One row per lead with a definite outcome: pushed (with its Instantly lead ID), dropped before sending (with the reason), or failed (batch error — safe to re-run; the skip flags stop double-sends). Use Export to download as CSV or Excel.

# 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 = {
    "targetType": "campaign",
    "campaignId": "",
    "listId": "",
    "createListIfMissing": false,
    "newListName": "",
    "datasetId": "jzBc4FybmLuK4QC8Y",
    "maxLeads": 100,
    "fieldMapping": {},
    "customVariableColumns": [],
    "sendCustomVariables": true,
    "skipIfInWorkspace": false,
    "skipIfInCampaign": true,
    "skipIfInList": true,
    "dedupeByEmail": true,
    "verifyLeadsOnImport": false,
    "blocklistId": "",
    "batchSize": 500,
    "dryRun": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("programmx/instantly-lead-pusher").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 = {
    "targetType": "campaign",
    "campaignId": "",
    "listId": "",
    "createListIfMissing": False,
    "newListName": "",
    "datasetId": "jzBc4FybmLuK4QC8Y",
    "maxLeads": 100,
    "fieldMapping": {},
    "customVariableColumns": [],
    "sendCustomVariables": True,
    "skipIfInWorkspace": False,
    "skipIfInCampaign": True,
    "skipIfInList": True,
    "dedupeByEmail": True,
    "verifyLeadsOnImport": False,
    "blocklistId": "",
    "batchSize": 500,
    "dryRun": True,
}

# Run the Actor and wait for it to finish
run = client.actor("programmx/instantly-lead-pusher").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 '{
  "targetType": "campaign",
  "campaignId": "",
  "listId": "",
  "createListIfMissing": false,
  "newListName": "",
  "datasetId": "jzBc4FybmLuK4QC8Y",
  "maxLeads": 100,
  "fieldMapping": {},
  "customVariableColumns": [],
  "sendCustomVariables": true,
  "skipIfInWorkspace": false,
  "skipIfInCampaign": true,
  "skipIfInList": true,
  "dedupeByEmail": true,
  "verifyLeadsOnImport": false,
  "blocklistId": "",
  "batchSize": 500,
  "dryRun": true
}' |
apify call programmx/instantly-lead-pusher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,programmx/instantly-lead-pusher"
        }
    }
}

```

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/7vfsIBgfnWypEEXUN/builds/sGcNc4XwI0f8DSV3j/openapi.json
