Outreach Sequencer avatar

Outreach Sequencer

Pricing

Pay per usage

Go to Apify Store
Outreach Sequencer

Outreach Sequencer

Pricing

Pay per usage

Rating

0.0

(0)

Developer

ryan clinton

ryan clinton

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

4 hours ago

Last modified

Categories

Share

Outreach Sequencer — Multi-Provider Email Sequences

Cold email outreach sequencer that sends personalized 3-step sequences via SMTP, SendGrid, or Mailgun — with automated day-3 and day-7 follow-ups. Built for sales teams, marketing agencies, and recruiters who need structured, scalable cold email campaigns without a subscription to heavyweight outreach platforms. Sequence state persists across runs in Apify KV Store, so follow-ups fire exactly on schedule regardless of when you run the actor.

This actor handles the full sequence lifecycle: send the initial email, wait three days, send the first follow-up, wait four more days, send the final touch. All three emails are personalized per contact using {{firstName}}, {{companyName}}, {{topService}}, and {{summarySnippet}} template variables. No code required to get started — configure your provider credentials, paste your templates, and run.

What data can you extract?

Every email dispatch produces a structured output record you can download as JSON or CSV.

Data PointSourceExample
📧 Recipient emailLead inputjames.walker@pinnacletech.io
👤 First nameLead inputJames
🏢 Company nameLead inputPinnacle Tech
Send statusProvider responsesent
🔢 Sequence stepActor logic1 (initial), 2 (day 3), 3 (day 7)
🔑 Sequence IDUUID v4 generated per contacta3f7c219-84bb-4e12-9c3d-ff217a8b10cd
📝 Rendered subjectTemplate engineQuick question for Pinnacle Tech
📅 Follow-up scheduledSequence statetrue
🕐 Next follow-up timestampSequence state2024-11-18T09:14:22.000Z
⚠️ Error messageProvider responsenull
🧪 Dry run flagInputfalse
🕓 Processed atActor run2024-11-15T09:14:22.000Z

Why use Outreach Sequencer?

Setting up cold email sequences manually means copy-pasting personalized emails, tracking who received which step in a spreadsheet, and remembering to send follow-ups three or seven days later. Mistakes are routine: duplicates go out, follow-ups reach people who replied, contacts get skipped entirely. Dedicated outreach platforms charge $37–149/month per seat and lock you into their sending infrastructure with limited provider choice.

This actor automates the entire sequence lifecycle. You bring your own sending credentials — any SMTP-capable provider, SendGrid API v3, or Mailgun API. The actor tracks every sequence in Apify KV Store and handles all the timing logic for you.

  • Scheduling — run on a daily Apify schedule; advance mode detects and sends only the follow-ups that are actually due
  • API access — trigger runs from Python, JavaScript, or any HTTP client via the Apify API
  • Provider flexibility — use Brevo, Gmail Workspace, AWS SES, SendGrid, Mailgun, or any SMTP server with no lock-in
  • Monitoring — get Slack or email alerts when runs fail or email counts drop unexpectedly
  • Integrations — push output records to Google Sheets, HubSpot, or webhooks via Zapier or Make

Features

  • Three sending providers — SMTP (nodemailer, port 587 STARTTLS or 465 SSL), SendGrid REST API v3 (POST /v3/mail/send, 202-acknowledged), and Mailgun Messages API (HTTP Basic auth, application/x-www-form-urlencoded) — all return the same output shape
  • Two-mode operationstart mode sends step 1 to new leads; advance mode scans the KV store for sequences where nextFollowUpAt <= now() and dispatches the next due step
  • Persistent sequence state — each contact's state (step1SentAt, step2SentAt, step3SentAt, repliedAt, unsubscribedAt, nextFollowUpAt) is stored in a named Apify KV Store, keyed by base64url-encoded email address — survives run restarts and scheduling gaps
  • UUID v4 sequence IDs — every contact gets a stable, unique sequenceId generated at first contact; used in unsubscribe URLs for per-contact opt-out tracking
  • Day-3 and day-7 follow-up scheduling — step 2 fires 3 days after step 1; step 3 fires 7 days after step 1, calculated from the original send baseline so the sequence always spans exactly 7 days from first contact
  • {{variable}} template engine — five substitution variables available in all templates: {{firstName}}, {{companyName}}, {{topService}}, {{summarySnippet}}, {{unsubscribeLink}}; unsubscribe URL renders before insertion so {{sequenceId}} and {{email}} inside the URL resolve correctly
  • Automatic deduplication — contacts with step1SentAt already set are skipped in start mode; unsubscribed or replied contacts are skipped in advance mode
  • Per-run rate limiting — hard cap via rateLimitPerRun (max 10,000); excess leads get status: deferred in the dataset with no charge
  • Dry-run mode — renders templates and logs output without dispatching any emails or incurring PPE charges; use to verify personalization before going live
  • Spending limit compliance — stops immediately when the Apify per-run charge limit is reached; dataset record is written before each charge event so no data is lost
  • Summary records — each run appends a type: "summary" record with totals (sent, failed, deferred, skipped) for easy monitoring
  • Multi-campaign isolation — set a unique kvStoreName per campaign so sequences from different campaigns never collide
  • Environment variable credential fallback — SMTP credentials and API keys fall back to SMTP_USER, SMTP_PASS, EMAIL_API_KEY environment variables if not provided in input, enabling secrets management through Apify environment variables

Use cases for cold email outreach sequencer

Sales prospecting and SDR outreach

Sales development reps building outbound pipelines spend hours manually sending initial emails and tracking follow-up timing in CRMs. Feed a list of enriched leads — with firstName, companyName, and a summarySnippet from research — into start mode, then schedule advance mode to run daily. The actor sends each contact's next due step automatically, freeing reps to focus on replies rather than logistics.

Marketing agency lead generation

Agencies building prospect lists for clients need to run outreach across multiple simultaneous campaigns. Use a separate kvStoreName per campaign to isolate sequences. Combine with Google Maps Email Extractor to source local business contacts, then pipe directly into this actor's leads array for immediate sequencing.

Recruiting and talent sourcing

Recruiters reaching out to passive candidates need structured multi-touch sequences as much as sales teams do. Personalize summarySnippet with the role or opportunity context, and use {{topService}} (populated from the lead's servicesOffered array) to reference the candidate's primary skill. The followUpTemplate3 and followUpTemplate7 fields let you craft softer nudges as the sequence progresses.

B2B lead enrichment pipelines

Teams using Waterfall Contact Enrichment or Website Contact Scraper to build contact databases can feed enriched records directly into this actor's leads array. The output dataset links back to sequenceId, making it straightforward to join send activity against your CRM.

Conference and event follow-up

After collecting leads from a conference or trade show, use this actor to execute a personalized post-event sequence. The summarySnippet field carries context (e.g., what was discussed at the booth), and the three-step cadence matches standard conference follow-up best practice: day-of intro, day-3 value add, day-7 final ask.

Competitive intelligence outreach

Market researchers and intelligence teams at companies using Company Deep Research to profile target accounts can execute outreach to identified contacts with company-specific personalization pre-populated in the leads array.

How to send cold email sequences

  1. Choose your email provider and enter credentials — Select SMTP, SendGrid, or Mailgun. For SMTP, enter your host (e.g., smtp-relay.brevo.com), port (587), username, and password. For SendGrid or Mailgun, enter your API key and, for Mailgun, your verified sending domain.

  2. Paste your lead list — In the leads field, provide an array of contacts. Each contact needs at minimum an email address. Add firstName, companyName, servicesOffered, and summarySnippet to enable personalization in your templates.

  3. Write your templates — Enter your step-1 email body and subject. Use {{firstName}}, {{companyName}}, {{topService}}, and {{summarySnippet}} for personalization. Add {{unsubscribeLink}} in every template (required for CAN-SPAM compliance). Optionally fill in followUpTemplate3 and followUpTemplate7 for automated follow-ups.

  4. Run in start mode, then schedule advance mode — Click "Start" with mode: start to send the initial emails. Set up a daily scheduled run with mode: advance (no leads array needed) to send day-3 and day-7 follow-ups as they come due. Download results from the Dataset tab in JSON or CSV.

Input parameters

ParameterTypeRequiredDefaultDescription
modestring (enum)Yesstartstart — sends step 1 to new leads; advance — scans KV store and sends due follow-ups
leadsarrayYes (start mode)[]Array of lead objects: { email, firstName, companyName, servicesOffered[], summarySnippet }
emailProviderstring (enum)Yessmtpsmtp, sendgrid, or mailgun
smtpHoststringSMTP onlySMTP server hostname (e.g., smtp-relay.brevo.com, smtp.gmail.com)
smtpPortintegerSMTP only587587 for STARTTLS (recommended), 465 for SSL
smtpUserstringSMTP onlySMTP login username (usually your email address or API key username)
smtpPassstringSMTP onlySMTP password or SMTP-specific API key. Stored encrypted.
apiKeystringSendGrid / MailgunSendGrid: "Mail Send" API key. Mailgun: private API key starting with key-. Stored encrypted.
mailgunDomainstringMailgun onlyVerified Mailgun sending domain (e.g., mail.yourdomain.com)
fromNamestringYesSender display name shown in the From field (e.g., Jane at Acme Corp)
fromEmailstringYesVerified sender email address with SPF/DKIM/DMARC configured
unsubscribeUrlstringYesOpt-out endpoint URL. Supports {{sequenceId}} and {{email}} substitutions.
rateLimitPerRunintegerYes100Hard cap on emails per run. Excess leads get status: deferred. Range: 1–10,000.
emailTemplatestringYes(see prefill)Step-1 email body. Supports {{firstName}}, {{companyName}}, {{topService}}, {{summarySnippet}}, {{unsubscribeLink}}.
subjectLinestringYesQuick question for {{companyName}}Step-1 subject. Supports {{companyName}} and {{firstName}}.
followUpTemplate3stringNo""Day-3 follow-up body template. Leave blank to disable.
followUpTemplate7stringNo""Day-7 follow-up body template. Leave blank to disable.
followUpSubject3stringNo""Day-3 subject. Defaults to Re: {subjectLine} if blank.
followUpSubject7stringNo""Day-7 subject. Defaults to Re: {subjectLine} if blank.
kvStoreNamestringNooutreach-sequencesNamed KV store for sequence state. Use a unique name per campaign.
dryRunbooleanNofalseWhen true, templates render and log but no emails are sent and no charges apply.

Input examples

Simple single-step campaign via SMTP (most common):

{
"mode": "start",
"emailProvider": "smtp",
"smtpHost": "smtp-relay.brevo.com",
"smtpPort": 587,
"smtpUser": "yourlogin@example.com",
"smtpPass": "your-smtp-password",
"fromName": "Sarah at Pinnacle Digital",
"fromEmail": "sarah@pinnacledigital.io",
"unsubscribeUrl": "https://pinnacledigital.io/unsubscribe?sid={{sequenceId}}&e={{email}}",
"rateLimitPerRun": 50,
"subjectLine": "Quick question for {{companyName}}",
"emailTemplate": "Hi {{firstName}},\n\nI came across {{companyName}} and was impressed by your work in {{topService}}.\n\n{{summarySnippet}}\n\nWould you be open to a quick 15-minute call this week?\n\nBest,\nSarah\n\nOpt out: {{unsubscribeLink}}",
"kvStoreName": "campaign-nov-2024",
"dryRun": false,
"leads": [
{
"email": "james.walker@acmecorp.com",
"firstName": "James",
"companyName": "Acme Corp",
"servicesOffered": ["SEO", "PPC"],
"summarySnippet": "Your recent case study on reducing CPA by 40% for e-commerce brands caught my attention."
},
{
"email": "linda.chen@betaindustries.com",
"firstName": "Linda",
"companyName": "Beta Industries",
"servicesOffered": ["Content Marketing"],
"summarySnippet": "I noticed Beta Industries has been expanding into B2B SaaS content — an area where we've driven strong pipeline results."
}
]
}

Full 3-step sequence via SendGrid API:

{
"mode": "start",
"emailProvider": "sendgrid",
"apiKey": "SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"fromName": "Marcus at GrowthLab",
"fromEmail": "marcus@growthlab.co",
"unsubscribeUrl": "https://growthlab.co/opt-out?sid={{sequenceId}}&e={{email}}",
"rateLimitPerRun": 100,
"subjectLine": "Idea for {{companyName}}",
"emailTemplate": "Hi {{firstName}},\n\nI've been following {{companyName}}'s work in {{topService}} and had an idea I wanted to share.\n\n{{summarySnippet}}\n\nWorth a few minutes?\n\nBest,\nMarcus\n\nUnsubscribe: {{unsubscribeLink}}",
"followUpTemplate3": "Hi {{firstName}},\n\nJust bumping this to the top of your inbox in case it got buried.\n\nStill happy to share the idea — only takes 5 minutes.\n\nBest,\nMarcus\n\nUnsubscribe: {{unsubscribeLink}}",
"followUpSubject3": "",
"followUpTemplate7": "Hi {{firstName}},\n\nLast nudge on this — I know inboxes get hectic.\n\nIf the timing isn't right, no worries at all. I'll check back in a few months.\n\nBest,\nMarcus\n\nUnsubscribe: {{unsubscribeLink}}",
"followUpSubject7": "",
"kvStoreName": "growthlab-q4-outreach",
"dryRun": false,
"leads": [
{
"email": "priya.nair@deltasystems.com",
"firstName": "Priya",
"companyName": "Delta Systems",
"servicesOffered": ["Performance Marketing"],
"summarySnippet": "Delta Systems has been scaling paid acquisition aggressively — I wanted to share a framework we've used to offset rising CPCs."
}
]
}

Advance mode (daily scheduled run — no leads array needed):

{
"mode": "advance",
"emailProvider": "smtp",
"smtpHost": "smtp-relay.brevo.com",
"smtpPort": 587,
"smtpUser": "yourlogin@example.com",
"smtpPass": "your-smtp-password",
"fromName": "Sarah at Pinnacle Digital",
"fromEmail": "sarah@pinnacledigital.io",
"unsubscribeUrl": "https://pinnacledigital.io/unsubscribe?sid={{sequenceId}}&e={{email}}",
"rateLimitPerRun": 100,
"subjectLine": "Quick question for {{companyName}}",
"emailTemplate": "",
"followUpTemplate3": "Hi {{firstName}},\n\nJust following up on my note from a few days ago.\n\nWould love to connect — does 15 minutes work this week?\n\nBest,\nSarah\n\nOpt out: {{unsubscribeLink}}",
"followUpTemplate7": "Hi {{firstName}},\n\nOne last note — if the timing isn't right, completely understand. I'll reach out again next quarter.\n\nBest,\nSarah\n\nOpt out: {{unsubscribeLink}}",
"kvStoreName": "campaign-nov-2024",
"dryRun": false
}

Input tips

  • Test with dryRun: true first — templates render fully and log to the actor console, but no emails are sent and no charges apply. Verify personalization looks correct before going live.
  • Use a unique kvStoreName per campaign — sequences from different campaigns share state if they share a KV store name. Name each campaign distinctly (e.g., campaign-q4-2024-saas) to prevent collisions.
  • Schedule advance mode daily — set up a recurring Apify schedule with mode: advance running every 24 hours. It only sends emails that are actually due, so running it daily has no side effects on contacts not yet ready for a follow-up.
  • Respect your provider's daily sending limits — Brevo free tier allows 300 emails/day; SendGrid free tier allows 100/day. Set rateLimitPerRun below your provider's daily cap. Deferred leads remain in the dataset with status: deferred.
  • Leave emailTemplate blank in advance mode — the step-1 template is not used when scanning for follow-ups. Only followUpTemplate3 and followUpTemplate7 are dispatched in advance mode.

Output example

{
"email": "james.walker@acmecorp.com",
"companyName": "Acme Corp",
"firstName": "James",
"status": "sent",
"step": 1,
"sequenceId": "a3f7c219-84bb-4e12-9c3d-ff217a8b10cd",
"subject": "Quick question for Acme Corp",
"followUpScheduled": true,
"nextFollowUpAt": "2024-11-18T09:14:22.000Z",
"error": null,
"dryRun": false,
"processedAt": "2024-11-15T09:14:22.000Z"
}

Each run also appends a summary record:

{
"type": "summary",
"mode": "start",
"totalLeads": 50,
"sent": 47,
"failed": 1,
"deferred": 2,
"skipped": 0,
"dryRun": false,
"completedAt": "2024-11-15T09:19:04.000Z"
}

Output fields

FieldTypeDescription
emailstringRecipient email address (normalized to lowercase)
companyNamestringCompany name from lead input
firstNamestringFirst name from lead input
statusstringsent, failed, deferred, dry-run, or skipped
stepintegerSequence step: 1 (initial), 2 (day-3 follow-up), 3 (day-7 follow-up)
sequenceIdstringUUID v4 assigned to this contact at step 1; stable across all steps
subjectstringFully rendered subject line with all variables substituted
followUpScheduledbooleantrue if a future follow-up step has been scheduled in KV store
nextFollowUpAtstring or nullISO 8601 timestamp when the next follow-up becomes due
errorstring or nullProvider error message if status is failed; otherwise null
dryRunbooleanWhether this record was produced in dry-run mode
processedAtstringISO 8601 timestamp when this record was created
typestringPresent only on summary records: "summary"
modestringPresent only on summary records: "start" or "advance"
totalLeadsintegerPresent only on start-mode summary: total leads in input array
totalSequencesScannedintegerPresent only on advance-mode summary: KV store keys examined
sentintegerPresent only on summary records: emails successfully dispatched
failedintegerPresent only on summary records: emails that returned a provider error
deferredintegerPresent only on start-mode summary: leads not sent due to rate limit
skippedintegerPresent only on summary records: contacts skipped (duplicate, unsubscribed, replied)

How much does it cost to send cold email sequences?

Outreach Sequencer uses pay-per-event pricing — you pay $0.05 per email successfully dispatched to any provider. Dry-run mode is not charged. Platform compute costs are included.

ScenarioEmails sentCost per emailTotal cost
Quick test (10 contacts, step 1 only)10$0.05$0.50
Small campaign (50 contacts, step 1)50$0.05$2.50
Medium campaign (200 contacts, all 3 steps)600$0.05$30.00
Large campaign (500 contacts, all 3 steps)1,500$0.05$75.00
Enterprise (2,000 contacts, all 3 steps)6,000$0.05$300.00

You can set a maximum spending limit per run to control costs. The actor stops when your budget is reached; contacts that did not receive an email get status: deferred in the output dataset.

Compare this to dedicated outreach platforms: Lemlist charges $59/month per seat, Instantly charges $37/month, Reply.io charges $60/month — all with annual commitments and platform lock-in. With this actor, a 200-contact, 3-step campaign costs $30 with no subscription and no seat fees. Most users spend $5–40/month depending on campaign volume.

Send cold email sequences using the API

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("ryanclinton/outreach-sequencer").call(run_input={
"mode": "start",
"emailProvider": "smtp",
"smtpHost": "smtp-relay.brevo.com",
"smtpPort": 587,
"smtpUser": "yourlogin@example.com",
"smtpPass": "your-smtp-password",
"fromName": "Sarah at Pinnacle Digital",
"fromEmail": "sarah@pinnacledigital.io",
"unsubscribeUrl": "https://pinnacledigital.io/unsubscribe?sid={{sequenceId}}&e={{email}}",
"rateLimitPerRun": 50,
"subjectLine": "Quick question for {{companyName}}",
"emailTemplate": "Hi {{firstName}},\n\nI noticed {{companyName}}'s work in {{topService}}.\n\n{{summarySnippet}}\n\nWould a quick call be worth your time?\n\nBest,\nSarah\n\nOpt out: {{unsubscribeLink}}",
"kvStoreName": "campaign-api-test",
"dryRun": False,
"leads": [
{
"email": "james.walker@acmecorp.com",
"firstName": "James",
"companyName": "Acme Corp",
"servicesOffered": ["SEO"],
"summarySnippet": "Your recent blog post on organic growth caught my attention."
}
]
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item.get("type") == "summary":
print(f"Summary — sent: {item['sent']}, failed: {item['failed']}, deferred: {item['deferred']}")
else:
print(f"{item['email']} | step {item['step']} | status: {item['status']} | next follow-up: {item.get('nextFollowUpAt')}")

JavaScript

import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: "YOUR_API_TOKEN" });
const run = await client.actor("ryanclinton/outreach-sequencer").call({
mode: "start",
emailProvider: "sendgrid",
apiKey: "SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
fromName: "Marcus at GrowthLab",
fromEmail: "marcus@growthlab.co",
unsubscribeUrl: "https://growthlab.co/opt-out?sid={{sequenceId}}&e={{email}}",
rateLimitPerRun: 100,
subjectLine: "Idea for {{companyName}}",
emailTemplate: "Hi {{firstName}},\n\nI had an idea for {{companyName}} in {{topService}}.\n\n{{summarySnippet}}\n\nWorth 10 minutes?\n\nBest,\nMarcus\n\nUnsubscribe: {{unsubscribeLink}}",
followUpTemplate3: "Hi {{firstName}},\n\nJust bumping this up — let me know if the timing works.\n\nBest,\nMarcus\n\nUnsubscribe: {{unsubscribeLink}}",
kvStoreName: "growthlab-q4",
dryRun: false,
leads: [
{
email: "priya.nair@deltasystems.com",
firstName: "Priya",
companyName: "Delta Systems",
servicesOffered: ["Performance Marketing"],
summarySnippet: "Delta Systems' recent expansion into B2B SaaS caught my attention — we've driven strong pipeline results in that space."
}
]
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
if (item.type === "summary") {
console.log(`Summary — sent: ${item.sent}, failed: ${item.failed}`);
} else {
console.log(`${item.email} | step ${item.step} | ${item.status} | followUp: ${item.nextFollowUpAt ?? "none"}`);
}
}

cURL

# Start the actor run (start mode — sends step 1)
curl -X POST "https://api.apify.com/v2/acts/ryanclinton~outreach-sequencer/runs?token=YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mode": "start",
"emailProvider": "smtp",
"smtpHost": "smtp-relay.brevo.com",
"smtpPort": 587,
"smtpUser": "yourlogin@example.com",
"smtpPass": "your-smtp-password",
"fromName": "Sarah at Pinnacle Digital",
"fromEmail": "sarah@pinnacledigital.io",
"unsubscribeUrl": "https://pinnacledigital.io/unsubscribe?sid={{sequenceId}}&e={{email}}",
"rateLimitPerRun": 50,
"subjectLine": "Quick question for {{companyName}}",
"emailTemplate": "Hi {{firstName}},\n\nI noticed {{companyName}} is growing in {{topService}}.\n\n{{summarySnippet}}\n\nWould a brief call be worth your time?\n\nBest,\nSarah\n\nOpt out: {{unsubscribeLink}}",
"kvStoreName": "campaign-curl-test",
"dryRun": false,
"leads": [{"email": "james.walker@acmecorp.com","firstName": "James","companyName": "Acme Corp","servicesOffered": ["SEO"],"summarySnippet": "Your work in organic growth is impressive."}]
}'
# Fetch results (replace DATASET_ID from the run response)
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?token=YOUR_API_TOKEN&format=json"

How Outreach Sequencer works

Mode: start — initial email dispatch

When mode is start, the actor iterates the leads array in order. For each lead, it normalizes the email to lowercase and checks the named KV store for an existing sequence state keyed by base64url(email). If step1SentAt is already set, the contact is skipped. Otherwise, a new SequenceState is created with a fresh UUID v4 sequenceId and the lead's personalization data (firstName, companyName, topService from servicesOffered[0], summarySnippet).

The template engine performs ordered {{token}} substitution using a split-join method (no regex) to avoid escaping issues with special characters. Data variables substitute first. The unsubscribe URL renders separately — with {{sequenceId}} and {{email}} resolved inside it — before being inserted as {{unsubscribeLink}} in the email body. The rendered email then dispatches to the configured provider: SMTP via nodemailer with 30-second connection and socket timeouts, SendGrid via POST /v3/mail/send expecting HTTP 202, or Mailgun via POST /{domain}/messages with HTTP Basic auth.

State is persisted to the KV store only on sent or dry-run status. The output dataset record is pushed before the PPE charge event to ensure data is never lost if charging fails.

Mode: advance — follow-up scheduling

When mode is advance, the actor uses the Apify client to paginate all KV store keys in batches of 1,000. For each key it loads the sequence state and calls dueStep(): the function returns 2 if step1SentAt is set, step2SentAt is null, and nextFollowUpAt <= now(); returns 3 if step2SentAt is set, step3SentAt is null, and nextFollowUpAt <= now(); returns null otherwise. Contacts with unsubscribedAt or repliedAt set are always skipped.

Day-3 follow-up timing is set as step1SentAt + 3 days when step 1 is dispatched. Day-7 follow-up timing is calculated from the original step1SentAt baseline, not the step-2 send time — so the total sequence always spans 7 days from first contact regardless of when the actor last ran. After step 3, nextFollowUpAt is set to null and the sequence is complete.

KV store state management

Each contact's sequence state is a JSON object stored at a base64url-encoded key derived from the normalized email address. The schema tracks sequenceId, email, companyName, all three step timestamps, and the repliedAt and unsubscribedAt fields. You can set these fields externally via the Apify KV Store API to suppress future emails for any contact. Multiple campaigns run in parallel using separate kvStoreName values with complete isolation.

Tips for best results

  1. Verify SPF, DKIM, and DMARC before your first send. Deliverability depends entirely on your domain configuration, not the actor. Use mail-tester.com or a similar tool to score your setup before running a live campaign.

  2. Start with rateLimitPerRun: 10 and dryRun: true first. Check that templates render correctly for each lead, then disable dry run and increase the limit for the live run. Catching a broken template after 200 sends is expensive to recover from.

  3. Keep summarySnippet under 2 sentences. The variable substitutes inline — long snippets make emails feel templated. Aim for one specific observation about the company, not a generic pitch paragraph.

  4. Match rateLimitPerRun to your provider's daily limit minus a buffer. If your Brevo account sends 300/day across all campaigns, set this actor's limit to 200 to leave headroom for transactional email.

  5. Use a unique kvStoreName per campaign cadence. Running a new campaign to a partially overlapping list? Use a new KV store name so prior sequence state from the old campaign does not affect new sequences.

  6. Combine with Bulk Email Verifier before sending. Sending to invalid addresses damages sender reputation. Verify your list first and remove hard bounces before they reach this actor.

  7. Set the advance-mode schedule to run at the same time each day. The dueStep() check is a simple nextFollowUpAt <= now() comparison. Running the schedule at 9 AM daily means follow-ups send within 24 hours of becoming due.

  8. Mark replies and unsubscribes in the KV store promptly. Use the Apify KV Store API to set repliedAt or unsubscribedAt on a contact's state record when you receive a reply or opt-out. The advance mode checks these flags before every dispatch.

Combine with other Apify actors

ActorHow to combine
Website Contact ScraperScrape emails and company information from prospect websites, then pipe directly into this actor's leads array for immediate step-1 outreach
Google Maps Email ExtractorExtract local business contacts from Google Maps searches, then sequence them as a geographically targeted outreach campaign
Bulk Email VerifierVerify emails via MX and SMTP checks before sending to avoid hard bounces that damage sender reputation
Waterfall Contact EnrichmentEnrich a list of company names into full contact records (email, name, role) ready for the leads array
B2B Lead QualifierScore leads 0–100 from 30+ signals before sequencing — filter to high-scorers only for premium outreach batches
HubSpot Lead PusherPush output dataset records into HubSpot after each run to sync sequence activity against your CRM contacts
Email Pattern FinderDetect the email naming convention for a target company domain before building your lead list

Limitations

  • Plain text emails only — the actor sends text/plain bodies, not HTML. This is intentional for cold outreach (plain text has better deliverability for cold contact and avoids spam filters triggered by heavy HTML), but means no formatted layouts, images, or styled links.
  • No reply detection — the actor does not monitor inboxes. You must set repliedAt on a contact's KV store record manually (or via a webhook from your email provider) to prevent follow-ups after a reply.
  • No unsubscribe endpoint provided — the actor generates {{unsubscribeLink}} URLs with sequenceId and email embedded, but you must build and host the opt-out endpoint yourself. Setting unsubscribedAt in KV store state must also be handled by your system.
  • Mailgun US region only — the Mailgun provider uses api.mailgun.net (US region). EU-region Mailgun customers should use the SMTP provider with smtpHost: smtp.eu.mailgun.org instead.
  • No built-in scheduling — the actor does not self-schedule. You must create an Apify schedule manually to run advance mode daily for follow-ups to fire automatically.
  • No attachment support — email attachments are not supported. Link to hosted files in the email body instead.
  • Rate limits are per-run, not per-day — if you trigger start mode multiple times in a day, each run has its own rateLimitPerRun cap. You are responsible for staying within your provider's daily sending limits across all runs.
  • No A/B testing at the actor level — for split-testing subject lines or templates, run the actor twice with different configurations and separate kvStoreName values.

Integrations

  • Zapier — trigger a Zap when a run completes to push sent records into a Google Sheet or create HubSpot deals for new sequences
  • Make — build a scenario that watches the output dataset and sets repliedAt in KV store when your inbox integration detects a reply
  • Google Sheets — export the output dataset after each run to maintain a live campaign tracker spreadsheet
  • Apify API — trigger start and advance runs programmatically from your CRM, marketing automation platform, or lead management system
  • Webhooks — fire a webhook when each run finishes to notify your team in Slack or trigger downstream data pipeline steps
  • LangChain / LlamaIndex — generate personalized summarySnippet values per lead using an LLM agent, then pass the enriched lead array into this actor's input

Troubleshooting

  • Emails show status: failed with SMTP credentials error — confirm that smtpHost, smtpUser, and smtpPass are all provided. For Brevo, the SMTP user is your Brevo login email and the password is a Brevo SMTP key (not your Brevo account password). For SendGrid SMTP, use apikey as the username and your API key as the password.

  • SendGrid returns a non-202 status — the actor surfaces error messages from the SendGrid API response body. Common causes: API key missing "Mail Send" permission; sender email not verified in your SendGrid account; or recipient address on SendGrid's suppression list. Check your SendGrid Activity Feed for per-message detail.

  • Follow-ups not sending in advance mode — confirm that followUpTemplate3 or followUpTemplate7 is non-empty in the advance-mode run input. The actor requires the template to be present in the run that sends the follow-up, not just the run that sent step 1. Also confirm enough time has elapsed — day-3 follow-ups only fire once nextFollowUpAt <= now().

  • Contacts being skipped unexpectedly — in start mode, contacts with an existing step1SentAt in KV store are always skipped to prevent duplicates. To re-enqueue a contact after a bounced first send, delete their KV store record via the Apify console or API before re-running.

  • Template renders with blank {{topService}} — this field is populated from the first item of the lead's servicesOffered array. If servicesOffered is absent or empty in the lead object, {{topService}} renders as an empty string. Ensure the field is included in your leads input.

Responsible use

  • This actor sends emails on your behalf using credentials you provide. You are responsible for compliance with all applicable laws in your jurisdiction and the recipient's jurisdiction.
  • Include a working {{unsubscribeLink}} in every template. CAN-SPAM requires a functioning opt-out mechanism in every commercial email, and you must honor opt-outs within 10 business days.
  • Comply with GDPR, CASL, and other regional data protection and anti-spam regulations when sending to contacts in those jurisdictions.
  • Only send to contacts with a legitimate basis for outreach. Do not use purchased email lists without proper consent documentation.
  • Respect opt-outs immediately — set unsubscribedAt in KV store state as soon as a contact opts out, and do not send further emails to that contact.
  • For guidance on email outreach legality, see Apify's guide on web scraping and data use.

FAQ

How does the cold email sequencer handle follow-up timing? After step 1 is sent, the actor stores nextFollowUpAt = step1SentAt + 3 days in the KV store. When advance mode runs and finds nextFollowUpAt <= now(), it sends the day-3 follow-up and updates nextFollowUpAt = step1SentAt + 7 days. The day-7 timestamp is calculated from the original step-1 baseline, not the step-2 send time, so the total sequence always spans exactly 7 days from first contact.

Can I use this actor with Gmail SMTP? Yes. Set smtpHost: smtp.gmail.com, smtpPort: 587, smtpUser to your Gmail address, and smtpPass to a Gmail App Password — not your Google account password. You must enable 2-Step Verification and generate an App Password in Google Account Security settings. Note that Gmail has daily sending limits; check your organization's settings if using Google Workspace.

What happens if a contact replies? Will follow-ups still send? The actor does not monitor inboxes. To suppress follow-ups after a reply, set repliedAt on the contact's KV store record via the Apify KV Store API or a webhook from your email provider. The advance mode skips any contact where repliedAt is set.

How many contacts can I sequence in one run? There is no hard limit beyond rateLimitPerRun (max 10,000 per run) and the actor's 1-hour timeout. Each email dispatch takes 1–3 seconds including provider round-trip time. At 100 emails/run, expect 2–5 minutes. Large batches approaching 1,000 may get close to the timeout; split them across multiple scheduled runs if needed.

Can I run multiple campaigns simultaneously without them interfering? Yes. Set a unique kvStoreName for each campaign (e.g., campaign-q4-saas, campaign-nov-agencies). KV stores are completely isolated. Contacts in one campaign's store have no effect on contacts in another, even if the same email address appears in both.

How is this different from Lemlist, Instantly, or Reply.io? Dedicated outreach platforms charge $37–149/month per seat and require you to use their sending infrastructure. This actor charges $0.05 per email sent, accepts your own sending credentials (any SMTP provider, SendGrid, or Mailgun), runs inside Apify's scheduling and monitoring infrastructure, and produces structured output you can integrate with any downstream tool. There are no subscriptions, no seat limits, and no platform lock-in.

Is it legal to send cold outreach emails with this actor? Cold email legality depends on your jurisdiction and the recipient's location. In the US, CAN-SPAM permits cold B2B outreach if you include a physical address, a working opt-out link, and honor opt-outs promptly. In the EU, GDPR and ePrivacy rules are stricter — you generally need a documented legitimate interest basis before contacting individuals. You are responsible for your own compliance. See Apify's legal guide for more context.

Can I disable follow-ups and use this as a single-email sender? Yes. Leave both followUpTemplate3 and followUpTemplate7 blank. No nextFollowUpAt is scheduled after step 1 and no follow-ups will ever fire. The actor behaves as a personalized bulk email sender with deduplication and per-run rate limiting.

What happens if the actor hits the spending limit mid-run? The actor stops immediately when the Apify spending limit is reached. All contacts processed before the limit are recorded in the dataset with their status. Contacts not reached remain in the input list. Re-run the actor with the same lead list — contacts with step1SentAt already set will be skipped automatically, so only uncontacted leads are sent to.

Can I schedule this actor to run automatically? Yes. Create an Apify schedule for advance mode to run daily. For start mode, trigger it via the Apify API from your CRM or lead pipeline whenever a new batch of contacts is ready. See the Apify scheduling documentation for setup instructions.

Does the actor support HTML emails? No. All three providers send text/plain only. Plain text is deliberate — it avoids spam filter penalties that heavily templated HTML triggers and produces higher inbox placement for cold outreach. If you need HTML, you can extend the actor's source code or use your provider's template API separately.

What template variables are available? Five variables work in all templates: {{firstName}}, {{companyName}}, {{topService}} (first item in servicesOffered), {{summarySnippet}}, and {{unsubscribeLink}}. Subject lines support {{companyName}} and {{firstName}}. The unsubscribeUrl input field itself supports {{sequenceId}} and {{email}} for building per-contact opt-out links.

Help us improve

If you encounter issues, you can help us debug faster by enabling run sharing in your Apify account:

  1. Go to Account Settings > Privacy
  2. Enable Share runs with public Actor creators

This lets us see your run details when something goes wrong, so we can fix issues faster. Your data is only visible to the actor developer, not publicly.

Support

Found a bug or have a feature request? Open an issue in the Issues tab on this actor's page. For custom solutions or enterprise integrations, reach out through the Apify platform.