> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enrow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Lead Verification (n8n)

> Clean and verify email lists before campaigns with an n8n workflow that bulk-verifies leads using the Enrow API and exports a deliverable list

Verifying leads before a campaign cuts bounce rates and protects your sender reputation. This playbook builds an n8n workflow that reads an email list, verifies every address with the [Email Verifier](/api-reference/email-verifier/verify-bulk), categorizes the results (valid, invalid, risky), and exports a clean list ready to send. If you are new to n8n with Enrow, start with the [n8n getting started guide](/playbooks/n8n/getting-started).

## What will you build?

You will build a workflow that:

1. **Reads email list** from Google Sheets or CSV
2. **Verifies each email** using Enrow
3. **Categorizes results** (valid, invalid, risky)
4. **Exports clean list** ready for campaigns

**Time to build**: 10 minutes
**Difficulty**: Beginner

## When should you use this workflow?

Use this workflow whenever email deliverability matters to a downstream process. Common use cases:

* Clean email lists before marketing campaigns
* Verify form submissions in real-time
* Maintain database hygiene
* Reduce bounce rates

## What do you need before you start?

Before building the workflow, make sure you have the following:

* n8n ([get n8n](https://n8n.io))
* Enrow API key ([get key](https://app.enrow.io/settings/api-keys))
* Email list (Google Sheet or CSV)

The Enrow API key authenticates every request through the `x-api-key` header. See [Authentication](/authentication) for how to retrieve and pass the API key, and [Credits & billing](/credits-billing) to understand how verification credits are consumed.

## How do you set up the Google Sheet?

Create a sheet with email addresses and empty columns for the results:

| Email                                         | Status | Deliverable | Risk Factors |
| --------------------------------------------- | ------ | ----------- | ------------ |
| [john@example.com](mailto:john@example.com)   |        |             |              |
| [invalid@fake.com](mailto:invalid@fake.com)   |        |             |              |
| [temp@tempmail.com](mailto:temp@tempmail.com) |        |             |              |

## How do you build the workflow in 5 minutes?

The fastest path is to import a ready-made workflow, configure your credentials, and run it.

### Step 1: Import Workflow

Copy and import this workflow into n8n:

```json theme={null}
{
  "name": "Enrow - Email Verification",
  "nodes": [
    {
      "parameters": {},
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "read",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Sheet1!A:D"
      },
      "name": "Read Email List",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 3,
      "position": [460, 300]
    },
    {
      "parameters": {
        "batchSize": 100,
        "options": {}
      },
      "name": "Batch Emails",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [680, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.enrow.io/email/verify/bulk",
        "authentication": "headerAuth",
        "sendBody": true,
        "bodyContentType": "json",
        "jsonBody": "={\n  \"emails\": {{$json[\"emails\"].map(e => e.Email)}}\n}"
      },
      "name": "Verify Bulk",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [900, 300],
      "credentials": {
        "headerAuth": {"name": "Enrow API"}
      }
    },
    {
      "parameters": {
        "amount": 10,
        "unit": "seconds"
      },
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [1120, 300]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.enrow.io/email/verify/bulk?id={{$json[\"batch_id\"]}}",
        "authentication": "headerAuth"
      },
      "name": "Get Results",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [1340, 300],
      "credentials": {
        "headerAuth": {"name": "Enrow API"}
      }
    },
    {
      "parameters": {
        "operation": "update",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Sheet1!A:D"
      },
      "name": "Update Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 3,
      "position": [1560, 300]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [[{"node": "Read Email List", "type": "main", "index": 0}]]
    },
    "Read Email List": {
      "main": [[{"node": "Batch Emails", "type": "main", "index": 0}]]
    },
    "Batch Emails": {
      "main": [[{"node": "Verify Bulk", "type": "main", "index": 0}]]
    },
    "Verify Bulk": {
      "main": [[{"node": "Wait", "type": "main", "index": 0}]]
    },
    "Wait": {
      "main": [[{"node": "Get Results", "type": "main", "index": 0}]]
    },
    "Get Results": {
      "main": [[{"node": "Update Sheet", "type": "main", "index": 0}]]
    }
  }
}
```

### Step 2: Configure

1. Replace `YOUR_SHEET_ID` with your Google Sheet ID
2. Add your Enrow API key to Header Auth credential
3. Test with a few emails first

### Step 3: Run

1. Click **"Execute Workflow"**
2. Wait for verification to complete
3. Check your Google Sheet for results

## How do you build the workflow step by step?

If you prefer to build the workflow node by node, follow the detailed guide below.

### Step 1: Read Email List

**Google Sheets:**

1. Add **"Google Sheets"** node
2. Operation: **Read**
3. Select your spreadsheet
4. Range: `Sheet1!A:A` (email column only)

**CSV File:**

1. Add **"Read Binary Files"** node
2. Add **"CSV to JSON"** node
3. Map the email field

### Step 2: Create Batches

For better performance, process emails in batches:

1. Add **"Split In Batches"** node
2. Batch Size: **100** (or less for faster results)

This sends 100 emails at a time to Enrow.

### Step 3: Verify with Enrow Bulk API

1. Add **"HTTP Request"** node
2. Configure:

```javascript theme={null}
Method: POST
URL: https://api.enrow.io/email/verify/bulk
Authentication: Header Auth
  Name: x-api-key
  Value: YOUR_API_KEY

JSON Body:
{
  "emails": {{ $json["items"].map(item => item.Email) }}
}
```

This returns a `batch_id` immediately. See [Verify Bulk Emails](/api-reference/email-verifier/verify-bulk) for the full request and response reference.

### Step 4: Wait for Results

Add **"Wait"** node with 10-15 seconds delay. For large batches, wait longer. For event-driven retrieval instead of polling, pass a webhook URL in the request — see [How webhooks work](/how-webhooks-work).

### Step 5: Fetch Results

1. Add **"HTTP Request"** node
2. Configure:

```javascript theme={null}
Method: GET
URL: https://api.enrow.io/email/verify/bulk?id={{$json["batch_id"]}}
Authentication: Header Auth (reuse credential)
```

The [Get Bulk Verifications](/api-reference/email-verifier/get-bulk-verifications) endpoint returns the verification results for the batch.

### Step 6: Process Results

Add **"Function"** node to categorize emails:

```javascript theme={null}
const results = $input.all()[0].json.results;

const valid = results.filter(r =>
  r.status === 'valid' &&
  r.is_deliverable &&
  !r.metadata.is_disposable
);

const invalid = results.filter(r => r.status === 'invalid');

const risky = results.filter(r =>
  r.status === 'risky' ||
  r.metadata.is_disposable ||
  r.metadata.is_role
);

return [
  {
    json: {
      total: results.length,
      valid: valid.length,
      invalid: invalid.length,
      risky: risky.length,
      validEmails: valid.map(r => r.email),
      invalidEmails: invalid.map(r => r.email),
      riskyEmails: risky.map(r => r.email)
    }
  }
];
```

### Step 7: Update Google Sheet

1. Add **"Google Sheets"** node
2. Operation: **Update**
3. Map results back to original rows

Or create separate sheets for each category:

* **Valid Emails** → Sheet "Valid"
* **Invalid Emails** → Sheet "Invalid"
* **Risky Emails** → Sheet "Risky"

## How do you verify emails from a form in real time?

To verify emails as users submit forms, trigger the workflow with a webhook and verify each address individually:

```mermaid theme={null}
graph LR
    A[Webhook] --> B[Receive Form Data]
    B --> C[Verify Email]
    C --> D{Valid?}
    D -->|Yes| E[Save to CRM]
    D -->|No| F[Reject Submission]
```

### Workflow:

1. **Webhook Trigger**: Receives form submissions
2. **Extract Email**: Get email from form data
3. **Verify**: Call Enrow `/email/verify/single`
4. **Decision**:
   * Valid → Save to CRM (Salesforce, HubSpot, etc.)
   * Invalid → Send error response or flag for review

```javascript theme={null}
// In Function node after verification
if ($json.status === 'valid' && $json.is_deliverable) {
  return [
    {
      json: {
        action: 'accept',
        email: $json.email
      }
    }
  ];
} else {
  return [
    {
      json: {
        action: 'reject',
        email: $json.email,
        reason: 'Invalid or undeliverable email'
      }
    }
  ];
}
```

Single-address verification uses the [Verify Single Email](/api-reference/email-verifier/verify-single) endpoint. To push accepted leads into your CRM, see the [HubSpot](/integrations/crm/hubspot) and [Salesforce](/integrations/crm/salesforce) integrations.

## How do you export the clean list?

Once results are categorized, you can export the clean list in whichever format your stack needs.

### Option 1: Google Sheets

Already done! Results are in your sheet.

### Option 2: CSV Export

1. Add **"Move Binary Data"** node
2. Add **"Write Binary File"** node
3. Save to disk or send via email

### Option 3: Send to Email Platform

1. Add **"HTTP Request"** node for your email platform API
2. Examples:
   * **Mailchimp**: Add to audience
   * **SendGrid**: Update contacts
   * **Customer.io**: Sync attributes

## How do you track verification metrics?

Add a summary node to compute deliverability metrics for each run:

```javascript theme={null}
// Function node
const results = $input.all()[0].json.results;
const summary = {
  timestamp: new Date().toISOString(),
  total_verified: results.length,
  valid: results.filter(r => r.status === 'valid').length,
  invalid: results.filter(r => r.status === 'invalid').length,
  risky: results.filter(r => r.status === 'risky').length,
  disposable: results.filter(r => r.metadata?.is_disposable).length,
  role_based: results.filter(r => r.metadata?.is_role).length,
  free_provider: results.filter(r => r.metadata?.is_free).length
};

summary.valid_rate = (summary.valid / summary.total_verified * 100).toFixed(2) + '%';
summary.invalid_rate = (summary.invalid / summary.total_verified * 100).toFixed(2) + '%';

return [{ json: summary }];
```

Send this summary to:

* **Slack**: Post summary in channel
* **Email**: Daily/weekly reports
* **Database**: Track over time

## How do you reduce credit usage?

Verification consumes credits per email, so trim the work before each run. Three tactics keep costs down.

### Deduplicate Before Verifying

```javascript theme={null}
// Function node before verification
const emails = $input.all().map(item => item.json.Email);
const unique = [...new Set(emails)];

return unique.map(email => ({ json: { Email: email } }));
```

Removing duplicates saves credits by verifying each address only once.

### Cache Results

Store verification results for 30-90 days:

```javascript theme={null}
// Check cache before API call
const cached = await redis.get(`verified:${email}`);
if (cached) {
  return JSON.parse(cached);
}

// Call API
const result = await verifyEmail(email);

// Cache result
await redis.setex(`verified:${email}`, 7776000, JSON.stringify(result)); // 90 days

return result;
```

### Skip Already Verified

Only verify emails with empty status:

```javascript theme={null}
// IF node
{{$json["Status"]}} === "" || {{$json["Status"]}} === null
```

See [Credits & billing](/credits-billing) for the per-endpoint credit cost.

## Best Practices

<AccordionGroup>
  <Accordion title="Verify Before Campaigns">
    Always verify your list 24-48 hours before sending. Email validity can change.
  </Accordion>

  <Accordion title="Handle Risky Emails">
    Decide based on use case:

    * **B2B**: Role emails (info@, support@) might be OK
    * **B2C**: Reject disposable emails
    * **Cold outreach**: Only use high-confidence emails
  </Accordion>

  <Accordion title="Monitor Deliverability">
    Track bounce rates after sending. If high, re-verify your list.
  </Accordion>

  <Accordion title="Regular Cleaning">
    Verify your entire list every 3-6 months to remove invalid emails.
  </Accordion>
</AccordionGroup>

## Troubleshooting

**Verification taking too long?**

* Reduce batch size to 50-100 emails
* Increase wait time between batches

**Some emails showing as "risky"?**

* Check `metadata.is_disposable` - might be temporary emails
* Review `metadata.is_catch_all` - accept-all servers

**Getting rate limited?**

* Add delays between batches
* Reduce batch size
* Upgrade your Enrow plan

For request ceilings and `429` handling, see [Rate limits](/rate-limits). For status codes and error formats, see [Error handling](/error-handling).

## FAQ

<AccordionGroup>
  <Accordion title="How many emails can I verify in one batch?">
    Use the Batch Emails node to process emails in groups (the example uses a batch size of 100). For very large lists, split the work into smaller batches and add a longer wait between requests to avoid being rate limited. See [Rate limits](/rate-limits) for the request ceilings.
  </Accordion>

  <Accordion title="What do the valid, invalid, and risky statuses mean?">
    `valid` means the address is deliverable, `invalid` means it would bounce, and `risky` covers catch-all, disposable, or role-based addresses where deliverability is uncertain. Use the `metadata` fields (`is_disposable`, `is_role`, `is_catch_all`, `is_free`) to decide how to treat risky results for your use case.
  </Accordion>

  <Accordion title="Should I verify a single email or use bulk?">
    Use [Verify Bulk Emails](/api-reference/email-verifier/verify-bulk) for lists and scheduled cleaning, and [Verify Single Email](/api-reference/email-verifier/verify-single) for real-time checks such as form submissions.
  </Accordion>

  <Accordion title="How do I avoid wasting credits?">
    Deduplicate addresses before verifying, cache results for 30-90 days, and skip rows that already have a status. See [Credits & billing](/credits-billing) for per-endpoint costs.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Email Enrichment" icon="envelope-circle-check" href="/playbooks/n8n/email-enrichment-workflow">
    Find emails for your contacts with an n8n workflow.
  </Card>

  <Card title="Verify Bulk Emails" icon="code" href="/api-reference/email-verifier/verify-bulk">
    Explore the bulk verification endpoint reference.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    How to pass your API key in the x-api-key header.
  </Card>

  <Card title="Webhooks" icon="bell" href="/how-webhooks-work">
    Get notified automatically when a batch completes.
  </Card>
</CardGroup>
