# Spanish Business Email Finder (`rodimuxai/spanish-business-email-finder`) Actor

Find the verified contact email of Spanish businesses from their websites. Exploits the legally mandatory "Aviso Legal" page (LSSI law) that Google Maps and generic extractors miss. 84% hit rate measured on real businesses. Chain it after any Google Maps scraper. Pay only for emails found.

- **URL**: https://apify.com/rodimuxai/spanish-business-email-finder.md
- **Developed by:** [RodimuxAI](https://apify.com/rodimuxai) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 email founds

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

## Spanish Business Email Finder

**Find the contact email of Spanish businesses from their website.**

Google Maps gives you the name, phone, address and website. **It does not give you the email** — and email is the one field you can actually do outreach with at scale.

This Actor closes that gap: feed it websites (typically the output of any Google Maps scraper) and it returns the verified contact email of each business.

**84% hit rate, 100% precision** on a ground-truth sample of 43 real Spanish businesses, averaging 1.23 pages crawled per site. Only delivered emails are charged.

***

### Why it beats generic email extractors on Spanish sites

Three things generic extractors don't do:

**1. It exploits the Aviso Legal.** Spanish law (LSSI, art. 10) requires every commercial website in Spain to publish its contact details on a legal-notice page. That page holds the company's official email — and almost nobody crawls it. Here it gets top crawl priority and the highest scoring weight.

**2. It reads JSON-LD in the `<head>`.** WordPress + Yoast emits `schema.org/Organization` structured data with an `email` field. It lives in the `<head>`, so extractors that only read visible text miss it — and half of small-business Spain runs on WordPress. It's the highest-confidence source there is: the business declaring its own contact. In validation, JSON-LD decided 1 in 7 delivered emails.

**3. It understands the Spanish micro-business.** In a small clinic, the business email often *is* the owner's surname (`nasser@clinicanasser.es`). A naive filter throws it away for "looking personal". Here, if the prefix is part of the trade name, it's recognized as the business identity — not an employee.

### How to use it

**Option A — paste websites.** Paste the list into *Business websites*, hit Start, export the table as CSV or Excel when done.

**Option B — chain it after a Google Maps scraper.** Run any Maps scraper, copy that run's dataset ID and paste it into *Source dataset ID*. This Actor reads the websites straight from there: no CSV export, no copying columns by hand.

URLs are deduplicated by domain, so it doesn't matter if the source brings several pages of the same business: it's processed — and charged — once.

### What it does, in order

1. Opens the homepage and extracts candidates from `mailto:` links, JSON-LD and visible text (de-obfuscating `info (arroba) clinica.es`, `info [at] clinica.es`, `hola @ clinica . es`).
2. If it already has a business email, **it stops there** — no more pages spent.
3. Otherwise it queues same-domain pages by priority: legal notice → contact → privacy → about.
4. Scores all candidates (source, page type, domain match, role prefix) and picks the best.
5. Verifies the domain has MX records, discarding dead domains.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | — | Business websites. |
| `sourceDatasetId` | string | — | Run ID of another Actor (e.g. a Google Maps scraper). Websites are read from there directly. Alternative to `startUrls`. |
| `sourceDatasetField` | string | auto | Field holding the URL in that dataset. Empty = autodetected (`website`, `url`, `web`, `site`, `domain`…). |
| `maxPagesPerSite` | int | 6 | Page budget per site. |
| `includePersonalEmails` | bool | `false` | Include emails that look like a specific person's. See §Legal. |
| `requireMx` | bool | `true` | Discard emails whose domain can't receive mail. |
| `maxConcurrency` | int | 5 | Parallel requests. |

### Output

One record per website:

```json
{
  "domain": "raiolanetworks.es",
  "bestEmail": "info@raiolanetworks.es",
  "emails": ["info@raiolanetworks.es"],
  "foundOn": "https://raiolanetworks.es/",
  "sourceType": "jsonld",
  "emailKind": "role",
  "pagesVisited": 1,
  "status": "found"
}
```

*(Real output. The email lived only in the page's JSON-LD — a text-only extractor would have returned nothing.)*

`status` is `found`, `not_found` or `error`. `sourceType` tells you where it came from: `jsonld`, `legal`, `contact`, `privacy`, `about`, `other`.

### Pricing

**Pay per event: only `email-found` is charged.** If a site has no findable email, you don't pay for it. Failed attempts cost nothing.

### Legal — read before using

- By default **only business role emails are returned** (`info@`, `citas@`, `contacto@`, or the trade name). Emails that look like a specific person's are excluded unless you deliberately enable `includePersonalEmails`.
- A personal email is **personal data under GDPR** even if published. Published ≠ free to use.
- The Actor **respects each site's `robots.txt`**.
- **You are the data controller** for whatever you extract and how you use it. In Spain, commercial email is regulated by LSSI (art. 21) and GDPR: you need a legal basis, transparency and opt-out. This tool gives you the data; compliance is on you.
- Don't use it for unqualified mass cold mailing.

### Development

```bash
npm install
npm test                     # 18 tests of the pure functions, no network, no Apify

## Real local run: edit storage/key_value_stores/default/INPUT.json
node src/main.js
```

`src/extract.js` is all pure functions (extraction, classification, scoring) and tests without spending a run. `src/main.js` only orchestrates crawling and charging.

# Actor input Schema

## `startUrls` (type: `array`):

Websites to find emails for. Typically the output of a Google Maps scraper (`website` field).

## `sourceDatasetId` (type: `string`):

Alternative to pasting URLs: the run ID of a Google Maps scraper. Websites are read from there directly — no exporting or copying. If set, `startUrls` is optional.

## `sourceDatasetField` (type: `string`):

Field holding the URL in that dataset. Leave empty to autodetect (website, url, domain, web, site...).

## `maxPagesPerSite` (type: `integer`):

Pages visited per site before giving up. 6 covers home + contact + legal notice + privacy in most cases.

## `includePersonalEmails` (type: `boolean`):

By default only role emails are returned (info@, contacto@, citas@...). Emails that look like a specific person are personal data under GDPR and stay excluded unless you deliberately enable this.

## `requireMx` (type: `boolean`):

Checks the email domain has MX records, discarding dead domains. Does not verify the mailbox exists.

## `maxConcurrency` (type: `integer`):

Parallel requests. Lower it if a site blocks you.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://raiolanetworks.es"
    },
    {
      "url": "https://dinahosting.com"
    },
    {
      "url": "https://www.cdmon.com"
    },
    {
      "url": "https://sered.net"
    }
  ],
  "sourceDatasetId": "aBcDeFgHiJkLmNoPq",
  "sourceDatasetField": "",
  "maxPagesPerSite": 6,
  "includePersonalEmails": false,
  "requireMx": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `emails` (type: `string`):

One record per website, with bestEmail, foundOn, sourceType, emailKind and status (found / not\_found / error).

# 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 = {
    "startUrls": [
        {
            "url": "https://raiolanetworks.es"
        },
        {
            "url": "https://dinahosting.com"
        },
        {
            "url": "https://www.cdmon.com"
        },
        {
            "url": "https://sered.net"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rodimuxai/spanish-business-email-finder").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 = { "startUrls": [
        { "url": "https://raiolanetworks.es" },
        { "url": "https://dinahosting.com" },
        { "url": "https://www.cdmon.com" },
        { "url": "https://sered.net" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("rodimuxai/spanish-business-email-finder").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://raiolanetworks.es"
    },
    {
      "url": "https://dinahosting.com"
    },
    {
      "url": "https://www.cdmon.com"
    },
    {
      "url": "https://sered.net"
    }
  ]
}' |
apify call rodimuxai/spanish-business-email-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=rodimuxai/spanish-business-email-finder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/9lbu6X7b0Qrk676f1/builds/HCf3ANOJWLYEcSsbX/openapi.json
