Shopify Store Emails - Leads & Contacts from Domains avatar

Shopify Store Emails - Leads & Contacts from Domains

Pricing

from $1.05 / 1,000 store leads

Go to Apify Store
Shopify Store Emails - Leads & Contacts from Domains

Shopify Store Emails - Leads & Contacts from Domains

Shopify store emails from a domain list: public business email, phone, socials and tech stack per store; WooCommerce stores too. Non-Shopify or no-contact domains are not charged.

Pricing

from $1.05 / 1,000 store leads

Rating

0.0

(0)

Developer

Penny Data

Penny Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

Share

Shopify Leads Scraper: Store Emails & Contacts from Domains

Price: $1.50 per 1,000 stores with a contact found ($0.0015 each, pay per event). Stores with no contact, unsupported-platform and failed domains are free.

Try it in one click: open the Actor in Console. The input opens with 5 demo domains already filled in (gymshark.com, allbirds.com, fashionnova.com, kyliecosmetics.com, brooklinen.com); press Start.

No keyword search? Paste domains instead. Export store or website domains from your CRM, Google results or any spreadsheet and paste them in, one per line. Domains that are not Shopify (or WooCommerce) stores are skipped and are free, so you can paste a mixed list. The results table shows email, phone, lead score, socials, apps/tech stack, product count, theme and myshopify domain first.

Measured hit rate (100 domains, 2026-10-21)

One local run of this Actor's code (version with commit 133cb6b7) on 100 well-known brand domains, default settings (concurrency 5, robots.txt respected). The list is biased toward big, popular brands and about 20 domains are deliberately not Shopify (nike.com, amazon.com, walmart.com ...) as controls. Small independent stores behave differently, so treat this as a sample, not a market-wide rate.

measure (100 input domains)result
Detected as Shopify or WooCommerce53 (52 Shopify, 1 WooCommerce)
Stores with a public email22 (21 of the 52 Shopify stores, about 40%)
Stores with a phone9
Stores with social links46
Stores with any contact (charged)49
Detected, no contact (free)4
Not Shopify/WooCommerce (free)20
Blocked by the store's bot wall, HTTP 403/429 (free)16
Domain does not resolve (free)5
Connect/TLS errors (free)about 6

Cost of this run at $0.0015 per charged store: 49 stores, about $0.074 for the 100 input domains. A single run on one dev box, no deliverability check on the emails. The previous version on the same list found emails for 15 stores instead of 22, because transient DNS errors were wrongly reported as blocked addresses; that is fixed by retrying DNS and labelling real non-existent domains as "Domain does not resolve".

Shopify and WooCommerce store lead generation from a list of domains (each row has a platform field): one lead row per store with the public business email, phone, social links, product count, price range, currency and detected tech stack. Plain HTTP, no browser, no proxy. You are charged only for stores where at least one email, phone or social link was found. Stores with no contacts are returned with a note (no contact found, not charged), and sites that are neither Shopify nor WooCommerce, blocked or failed domains are returned with an error; none of those are charged.

You bring the store domains (one per line, or a CSV export); the actor finds the public email, phone, socials and tech stack of each. It does not search for stores by keyword. 1,000 stores with a contact = $1.50.

No 25-store cap: paste as many domains as you like in one run (default limit 500, raise maxDomains). No paid duplicates: each domain in your list is processed once and returned as one row. Failed or unsupported-platform domains are never charged. Flat $0.0015 per store with a contact found.

Free to try: a new Apify account includes free monthly platform credit, enough to test a few dozen stores before you pay anything.

Sample output: 8 domains, real local run (2026-10-21)

One run of this actor's code on 8 domains (maxConcurrency 1, maxProductPages 1), from a home connection with no proxy. Emails, phones and social links are exactly what each store publishes; - means the store published none. Product counts and price range read only the first page of 250 products (+ means more exist). Stores that rate-limit a run (HTTP 429) come back with an error and are not charged; see the FAQ below.

inputstore nameemailsphonessocials foundproducts (first page)price rangeresult
kyliecosmetics.comKylie Cosmetics--42461-300 USDcontact found
colourpop.comColourPop--3250+1-159 USDcontact found
tentree.comSustainable Clothingsupport@tentree.com-0250+4.5-348 USDcontact found
rothys.comRothy's-415-737-68492250+40-375 USDcontact found
allbirds.comAllbirdshelp@allbirds.com-6250+4-165 USDcontact found
fashionnova.comTrendy Clothes for Women, Men & Plus Sizes--4--contact found
brooklinen.comBrooklinen--0250+7.25-738 USDno contact found (not charged)
gymshark.comus.checkout.gymshark.com--0250+12-124 USDno contact found (not charged)

Six of the eight stores were charged ($0.0015 each, $0.009 in total); the two with no contact were free.

Quickstart (one API call)

curl -X POST "https://api.apify.com/v2/acts/pennydata~shopify-store-leads-contacts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domains":[{"url":"gymshark.com"}],"maxConcurrency":1}'

Or open the Actor in the Console and click Start: the prefilled input (five stores) runs as-is.

Example output (real run, 2026-10-20)

Input gymshark.com, output (one row, one store-lead event):

{
"input": "www.gymshark.com",
"domain": "gymshark.com",
"storeUrl": "https://www.gymshark.com",
"storeName": "Gymshark",
"description": "Unlock your full potential with our game-changing workout clothes. Shop gym clothing for the gym, running & everything in-between. Free delivery on orders over $75",
"myshopifyDomain": "gymsharkusa.myshopify.com",
"emails": ["press@gymshark.com", "support@gymshark.com"],
"phones": [],
"socials": {},
"technologies": ["Contentful", "Amazon CloudFront", "Next.js"],
"contactPagesChecked": ["/pages/about-us"],
"httpStatus": 200,
"nextStep": "Validate these emails in bulk: https://apify.com/pennydata/bulk-email-validator-no-smtp . Input: {\"emails\":[\"hello@example.com\"]}",
"capturedAt": "2026-10-20T04:40:09.275Z"
}

nextStep is a plain-text hint, present only on rows that have at least one email, pointing to our bulk email validator for checking the addresses, with a ready emails input for it. The run log ends with the same hint covering all collected emails. It is not charged and can be ignored.

Empty phones and socials are real: the actor returns only what the store itself publishes and never guesses.

Lead score (0-100)

Every ok row carries leadScore and leadScoreSignals (which signals added how many points), computed only from data already on the row: public email +35 (+5 for a second one), phone +15, social links +5 each (max 15), product count (+5 / +10 / +15 for 1-9 / 10-99 / 100+), detected technologies (+5 for 1-2, +10 for 3+), store description +5. Set minLeadScore (e.g. 50) to drop weaker stores from the dataset and the bill. The score is a simple contact-completeness heuristic, not a prediction of buying intent.

Status per row and run summary

Every dataset row has a status: ok (store detected; charged only when an email, phone or social link was found, otherwise the note says no contact found, not charged), skipped (reason: not_shopify, no Shopify/WooCommerce signals, not charged) or error (fetch failed, not charged). The key-value store record SUMMARY holds rows, ok, error, skipped (rows = dataset rows) and OUTPUT_SUMMARY holds the counts (received, duplicates removed, skipped, ok, no_contact, not_shopify, error, charged, estimated cost).

Inputs worth knowing: skipDomains (leave out stores you already have), maxEmailsPerSite (cap emails per store). Domains are normalised (scheme, www., trailing slash removed, lowercased) and de-duplicated before processing.

Migrating from automation-lab/shopify-scraper (and other Shopify lead actors)

Drop-in compatible with automation-lab/shopify-scraper input: paste its JSON (storeUrls, scrapeProducts, maxProducts, scrapeCollections, scrapeReviews, excludeTags...) and run; the store list is read from storeUrls and catalog-only fields are listed in the run log and ignored. applora/shopify-appstore-scraper takes App Store handles, not store domains, so its input cannot be pasted; use domains.

The input accepts the store-list field names other Shopify actors use, so you can paste an existing input and run it:

Field in other actorsUsed here as
storeUrls (e.g. automation-lab/shopify-scraper)store list, same as domains
startUrls, urlsstore list, same as domains
scrapeProductsincludeProductStats

Notes: this actor returns store contacts, not full catalogs or reviews, so catalog-only fields (scrapeCollections, scrapeReviews, excludeTags, maxProducts...) are ignored. Actors that scrape Shopify App Store listings (app handles, keywords) take a different kind of input (app handles, not store domains) and cannot be pasted directly; export the store domains you want and put them in domains.

Switching from dami_studio/shopify-store-leads

Paste your existing input unchanged. stores and startUrls are accepted as the store list (same as domains), maxConcurrency has the same name, and requestTimeoutSecs is read as timeoutSecs. defaultCountry, maxPagesPerStore, maxProducts, maxResponseSizeKb and proxyConfiguration are accepted but ignored (the run log lists them); this Actor has its own page and product limits.

{ "stores": ["allbirds.com", "https://www.gymshark.com"], "maxConcurrency": 5, "requestTimeoutSecs": 12 }

Price comparison, as listed on the Apify Store on 2026-10-21 (ours, dami_studio/shopify-store-leads; prices from the public Store API pricingInfos). The rival's listing shows a change from free to pay per event on 2026-10-25 00:00 UTC:

Per store lead (Free plan price)pennydata/shopify-store-leads-contactsdami_studio/shopify-store-leads
Until 2026-10-25$0.0015$0 (free)
From 2026-10-25$0.0015$0.0045
1,000 stores, from 2026-10-25$1.50$4.50

Differences: this Actor returns store contacts (emails, phones, socials), product count and price range and technologies, and charges only when a store has at least one email, phone or social link; it does not return the rival's other fields. Paid Apify plans get small discounts on the rival's price; check each Actor page for current prices. Not affiliated with dami_studio.

Switching from another Shopify leads actor

Two rivals on the Apify Store that take store-level input: contactminerlabs/shopify-email-scraper-advanced-cheapest-reliable (115 users) and intelscrape/shopify-owner-email (35 users), counts from the public Store API on 2026-10-21. Paste your saved input as is; fields this Actor does not use are accepted and listed in the run log instead of failing the run.

Rival fieldWhat happens here
MAX_ITEMS (contactminerlabs), maxResults (intelscrape; 0 = no limit there, so ignored here)caps stores processed, same as maxDomains (maxDomains wins if both are set)
startUrls (intelscrape)store list, same as domains
KEYWORD, DOMAIN_EMAILignored (logged); they search by keyword, this Actor takes your domains
nichePack, requireEmail, includeStoreIntel, activeWithinDays, minProducts, keywords, excludeKeywords, tldFilterignored (logged); stores with no email, phone or social link are never charged
maxProducts, maxPagesPerStore, proxyConfigurationignored (logged)
{ "startUrls": [{ "url": "https://allbirds.com" }], "maxResults": 50, "requireEmail": true }

Both rivals discover stores by keyword or niche; this Actor does not. Give it the store domains you already have and it returns emails, phones and socials. Not affiliated with either actor.

Migrating from clearpath/shopify-store-leads or ecommerce_leads/store-leads-14m-e-commerce-leads

Paste your existing input; it runs without edits. Those two actors search a store directory (category, country, filters), while this one enriches the store domains you give it, so add your list as domains (or storeUrls/startUrls/urls).

Field in the other actorWhat happens here
maxItems (clearpath)caps stores processed, same as maxDomains (maxDomains wins if both are set)
query, category, storeLocation, priceMin, priceMax, onSale, inStock, shipsFrom, shipsTo, skipPreviouslyScraped, leadListName, enrichWebsite, websiteEnrichmentMode, feedbackignored (logged); contact enrichment is always on
platform, page, country, city, emails, phones, technologies, apps, instagram, facebook, tiktok, youtube, linkedin, twitter, pinterest (ecommerce_leads)ignored (logged); we return emails, phones and socials per store, and stores with none are not charged

Example, a clearpath-style input plus your domains:

{ "query": "coffee", "maxItems": 100, "enrichWebsite": true, "domains": [{ "url": "https://allbirds.com" }] }

This actor does not discover stores; it needs your domains. Not affiliated with either actor.

How it works

  • Reads the homepage, /pages/contact, /pages/contact-us and /pages/about-us (only while no email is found) and the store's public /products.json.
  • Respects each store's robots.txt (on by default). Shopify's default robots.txt disallows /policies/, so those pages are skipped for most stores.
  • Business contacts only: by default only shared mailboxes a store publishes (info@, support@, hello@, sales@ ...). No names, no personal profiling, no social-profile scraping (only the links the store itself publishes).
  • Output per store: name, description, domain and myshopify domain, currency, theme, emails, phones, socials (Instagram, Facebook, X, TikTok, YouTube, Pinterest, LinkedIn), product count and price range, top vendors, technologies.

Track Black Friday discounts on any Shopify store

Set priceWatchMode to true to switch from contact leads to a price and discount snapshot. The actor reads each store's public /products.json and returns one row per variant:

FieldMeaning
priceCurrent price
compareAtPriceThe "was" price the store shows (only when higher than price)
discountAmount, discountPercentDifference and percent off
onSaletrue when compareAtPrice is above price
productTitle, productUrl, variantTitle, sku, availableWhich item it is and whether it is in stock

Example input (watch two competitors, keep only items at least 20% off):

{ "domains": [{ "url": "https://allbirds.com" }, { "url": "https://gymshark.com" }], "priceWatchMode": true, "onlyOnSale": true, "minDiscountPercent": 20, "maxProductPages": 4 }

Run it on an Apify schedule (for example daily from early November to Cyber Monday) and compare datasets to see when a competitor's discounts start, deepen or end. Billing: one store-lead event ($0.0015) per store that returns at least one row; stores with no matching variants, non-Shopify stores and failed fetches are free. The maxProductPages input caps how many pages of 250 products are read per store. Only public data the store itself serves is read, and robots.txt is respected by default.

Pricing

EventWhen it is chargedPrice
store-leadOne Shopify store with at least one email, phone or social link found (stores with no contacts are free)$0.0015

That is $1.50 per 1,000 stores. Stores with no contacts found, non-Shopify domains, robots.txt-blocked domains, rate-limited (HTTP 429) and failed stores are not charged. Cap spend with the maxCostUsd and maxDomains inputs (default 500 domains) or the run's maximum cost.

Use cases

  • Build an outreach list for apps, agencies and suppliers that serve Shopify merchants.
  • Qualify a list of store domains by product count, currency and detected tech.
  • Find published business emails and brand social links for a set of stores.
  • Enrich an existing CRM list of storefront domains.

Limits and honesty

  • Many stores publish their contact only through a form; then emails is empty. The actor never invents addresses.
  • Stores may rate-limit (HTTP 429) fast bursts. Those rows carry an error, are not charged, and can be re-run later; the default concurrency is low for this reason.
  • productCount reads up to maxProductPages x 250 products; when the limit is hit, productCountIsLowerBound is true.
  • You are responsible for using contact data lawfully (GDPR, CAN-SPAM, local marketing rules).

FAQ

How to scrape Shopify store emails?

Paste store domains into domains and click Start. For each store the actor reads the homepage and the /pages/contact, /pages/contact-us and /pages/about-us pages and returns the emails the store itself publishes. Nothing is guessed.

How to find a Shopify store's contact email and phone number?

Run the store's domain; emails and phones hold what the store publishes, and socials the links it publishes. Many stores publish only a contact form, so these can be empty (see Kylie Cosmetics and Fashion Nova in the sample).

How to check if a website is a Shopify store?

Run the domain. Non-Shopify sites come back as skipped with reason: not_shopify and are not charged. Shopify stores return their myshopifyDomain, theme and currency.

How much does a Shopify email scraper cost?

$0.0015 per store with at least one contact found, $1.50 per 1,000. Stores with no contact, non-Shopify domains and failed or rate-limited stores cost nothing. 200 stores is at most $0.30. Set maxCostUsd to cap a run.

Can I get a Shopify store's product count and price range?

Yes: productCount, priceMin, priceMax, topVendors and currency come from the store's public /products.json (first maxProductPages x 250 products; productCountIsLowerBound is true when the limit is hit).

Does it work for WooCommerce stores?

Yes, in a limited way. WooCommerce stores are detected from public homepage signals and returned as rows with platform: woocommerce, with the public email, phone and social links the store publishes. Product count and price range need the store's public Store API to be enabled (first 100 products), so they can be empty. A WooCommerce store is charged like any other store, only when a contact is found. Sites that are neither Shopify nor WooCommerce are skipped and not charged. In the 100-domain run above, 1 store was WooCommerce, so WooCommerce coverage is less tested than Shopify.

More questions

Where do the emails come from? Only addresses the store itself publishes on its homepage or contact/about pages. Nothing is guessed or generated.

Why is emails empty for some stores? Many stores publish only a contact form. A store is charged when at least one email, phone or social link is found; with none of those the row comes back as ok with a note and is free (no contact found, not charged).

Can I email these contacts? You are responsible for lawful use (GDPR, CAN-SPAM, local rules). Only shared business mailboxes are returned by default.

How much does a run cost? Worked example. $0.0015 per store with at least one contact found, nothing else on top. 1,000 domains where 600 return a contact = 600 x $0.0015 = $0.90. The OUTPUT_SUMMARY record shows charged and estimatedCostUsd after every run.

Does the limit stop the run? How do I cap spend? Yes, two ways. maxDomains (default 500, up to 100,000) processes only that many domains from your list and logs how many were left over. maxCostUsd stops the run once the charged leads would pass that amount; stores already in flight can finish, so the total can overshoot by a few events ($0.0015 each, up to maxConcurrency of them). The run's own maximum cost setting in the Console is also respected: when it is reached the run stops.

Is there a limit of 25, 100 or 200 stores per run? No hidden cap. The only limit is the maxDomains you set (default 500, maximum 100,000). If you give 5,000 domains, set maxDomains to 5000.

What if a run returns nothing, or I am charged for an empty result? You are charged per lead row only (a store where an email, phone or social link was found). Rows with no contact found, skipped or error status are never charged, so an all-empty run costs $0. Check OUTPUT_SUMMARY for the counts per status, and open the dataset: those rows are there with the reason.

How do I avoid duplicate leads across runs? Put stores you already have into skipDomains (one domain per entry, same format as domains). They are skipped and not charged. Inside one run, domains are normalised (scheme, www., trailing slash removed, lowercased) and de-duplicated, so www.x.com and x.com produce one row and one charge. The actor does not remember earlier runs by itself: keep your own list and pass it as skipDomains.

Am I charged for expired, closed or non-Shopify stores? No. Every domain is fetched live. A domain that is dead, blocks the fetch or returns an HTTP error comes back as error; a live site with no Shopify or WooCommerce signals comes back as skipped (reason: not_shopify). Neither is charged.

Can I get more than 100 results per run? Is there a max results cap? Yes. There is no 100-result limit and no max-results cap. A run returns one row per domain you give it, up to maxDomains (default 500, maximum 100,000). Set maxDomains to at least the length of your list.

Can it discover or search for Shopify stores by keyword, niche or country? (no store discovery) No. This Actor does no store discovery: it does not search or crawl for stores. Bring your own list of domains (CRM export, spreadsheet, store directory) and it returns the public contacts for each one.

Does this find Shopify stores for me? No. It does not discover stores. You bring the list of domains (from your own CRM, a store directory export, a spreadsheet or another source) and it returns the public contacts for each one.

Is it maintained? Yes. Report breakage in the Issues tab; failed rows and stores with no contacts are never billed.

Use from Claude or Cursor (MCP)

Apify hosts an MCP server, so an AI agent can run this Actor from a plain-English prompt. Add it to your MCP client (OAuth is the recommended sign-in, see the Apify MCP docs):

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=pennydata/shopify-store-leads-contacts"
}
}
}

?tools= limits the server to this Actor. Using a token instead of OAuth? Add "headers": { "Authorization": "Bearer <APIFY_TOKEN>" } next to url. The configurator at https://mcp.apify.com/ writes client-specific snippets; for Cursor you can also run apify mcp install cursor.

Example prompt: "Use shopify-store-leads-contacts on allbirds.com and gymshark.com. Give me a table of store name, public contact email, phone and status per domain, and flag any domain that is not a Shopify store or has no contact."

The agent passes the same input JSON as in the examples above and reads the dataset rows back. Billing is the usual pay-per-event pricing; set a maximum cost per run in the run options to cap what an agent can spend.

Call it with curl

curl -X POST "https://api.apify.com/v2/acts/pennydata~shopify-store-leads-contacts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domains":[{"url":"gymshark.com"}],"maxConcurrency":1}'

Call it from Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("pennydata/shopify-store-leads-contacts").call(run_input={"domains": [{"url": "gymshark.com"}], "maxConcurrency": 1})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item)

Schedules and integrations

Run it on a schedule from Apify Console (Schedules), or trigger it from your own pipeline through the API above. Finished runs can notify a webhook, and the dataset exports as JSON, CSV or Excel. Apify also offers integrations with Make, Zapier, n8n and Google Sheets that can start this Actor with the same input.


Built with AI assistance (Claude); maintained by the publisher.

WooCommerce notes

WooCommerce stores are detected from public homepage signals (generator meta tag, /wp-content/plugins/woocommerce/ assets). Product count and price range come from the store's public WooCommerce Store API (/wp-json/wc/store/v1/products) when the store leaves it enabled (first 100 products for the price range). Only public pages are read; there is no cart, checkout or login automation.

FAQ: why does a store return HTTP 429?

Some stores rate-limit automated requests. Those domains are returned with an error (HTTP 429) and are never charged. Re-run them later, or keep maxConcurrency at 1 (the setting used for the sample above).