agentsclimarketplace

Email verification

Skill puretechnyc/purebrain-skills/skills/development/email-verification

Verify and clean email lists before campaigns, CRM imports, or email platform uploads. DNS/MX/syntax/disposable/role-based checks. Includes Python implementation.From its SKILL.md

Install
npx -y skills add puretechnyc/purebrain-skills --skill email-verification

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.4 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it

Email Verification

Purpose

Verify and clean email lists using multi-level checks: syntax validation, DNS/MX lookup, disposable domain detection, role-based address flagging, and typo domain correction. Ensures only deliverable addresses reach your campaign tools.

When to Use

  • Before any cold outreach campaign push
  • Before CRM or list imports
  • Before email platform list uploads
  • When processing new lead lists from any source
  • When cleaning existing contact databases

Quick Start

Single email

python3 email_verifier.py --email [email protected]

CSV batch

python3 email_verifier.py \
  --input leads.csv \
  --email-column email \
  --output leads_verified.csv \
  --workers 20

Force DNS-only (skip SMTP probe)

python3 email_verifier.py \
  --input leads.csv \
  --email-column email \
  --output results.csv \
  --workers 20 \
  --skip-smtp

Parameters

ParameterFlagDescriptionDefault
Input file--input, -iCSV file with email column(required for batch)
Email column--email-column, -cColumn name containing emailsemail
Output file--output, -oOutput CSV path{input}_verified.csv
Single email--email, -eVerify one email (no CSV needed)--
Workers--workers, -wMax concurrent threads5
Skip SMTP--skip-smtpForce DNS-only modeauto-detected

Status Meanings

StatusMeaningAction
VALIDDomain has MX records, syntax OK, not disposableSend to campaign
INVALIDDead domain, no MX/A records, disposable provider, bad syntaxRemove from list
RISKYRole-based address (info@, admin@) or A-record-only domainSeparate low-priority sequence or exclude
UNKNOWNDNS timeout or lookup failureRe-verify later or exclude

Flags (in output CSV flags column)

FlagMeaning
ROLE_BASEDAddress is a role mailbox (info@, support@, admin@, etc.)
FREE_PROVIDERGmail, Yahoo, Outlook, etc. (relevant for B2B filtering)
NO_MX_A_ONLYDomain has A record but no MX record -- may not accept mail

Standard Workflow

  1. Verify: Run the tool on the lead CSV
  2. Split by status:
    • VALID -- push to primary campaign sequence
    • RISKY -- push to separate low-priority sequence, or exclude
    • INVALID -- remove entirely
    • UNKNOWN -- re-verify in next batch or exclude
  3. Push: Upload clean list to your email platform or CRM

Implementation

import dns.resolver
import re
import csv
import concurrent.futures
from datetime import datetime, timezone

# Disposable domain blocklist (subset -- expand as needed)
DISPOSABLE_DOMAINS = {
    'mailinator.com', 'guerrillamail.com', 'tempmail.com', 'throwaway.email',
    'yopmail.com', 'sharklasers.com', 'grr.la', 'guerrillamailblock.com',
    'pokemail.net', 'spam4.me', 'trashmail.com', 'dispostable.com',
    # Add 70+ more as needed
}

# Role-based prefixes
ROLE_PREFIXES = {
    'info', 'admin', 'support', 'sales', 'contact', 'help', 'billing',
    'accounts', 'webmaster', 'postmaster', 'abuse', 'noreply', 'no-reply',
    'team', 'office', 'hello', 'careers', 'jobs', 'press', 'media',
    'marketing', 'security', 'privacy', 'compliance', 'legal', 'hr',
    'feedback', 'inquiries', 'enquiries', 'service', 'orders',
}

# Free email providers
FREE_PROVIDERS = {
    'gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'aol.com',
    'icloud.com', 'protonmail.com', 'mail.com', 'zoho.com', 'yandex.com',
}

# Common typo corrections
TYPO_DOMAINS = {
    'gmial.com': 'gmail.com', 'gmai.com': 'gmail.com', 'gamil.com': 'gmail.com',
    'gmali.com': 'gmail.com', 'gmal.com': 'gmail.com', 'gnail.com': 'gmail.com',
    'yahooo.com': 'yahoo.com', 'yaho.com': 'yahoo.com',
    'outlok.com': 'outlook.com', 'outloo.com': 'outlook.com',
    'hotmal.com': 'hotmail.com', 'hotmial.com': 'hotmail.com',
}

def verify_email(email: str) -> dict:
    """Verify a single email address."""
    email = email.strip().lower()
    result = {
        'email': email,
        'status': 'UNKNOWN',
        'reason': '',
        'mx_host': '',
        'flags': [],
        'checked_at': datetime.now(timezone.utc).isoformat(),
    }

    # Syntax check
    if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
        result['status'] = 'INVALID'
        result['reason'] = 'Bad syntax'
        return result

    local, domain = email.rsplit('@', 1)

    # Typo domain check
    if domain in TYPO_DOMAINS:
        result['status'] = 'INVALID'
        result['reason'] = f'Typo domain -- did you mean {TYPO_DOMAINS[domain]}?'
        return result

    # Disposable domain check
    if domain in DISPOSABLE_DOMAINS:
        result['status'] = 'INVALID'
        result['reason'] = 'Disposable email provider'
        return result

    # Role-based check
    if local in ROLE_PREFIXES:
        result['flags'].append('ROLE_BASED')

    # Free provider check
    if domain in FREE_PROVIDERS:
        result['flags'].append('FREE_PROVIDER')

    # DNS/MX lookup
    try:
        mx_records = dns.resolver.resolve(domain, 'MX')
        result['mx_host'] = str(mx_records[0].exchange)
        result['status'] = 'VALID'
        result['reason'] = 'MX record found'
        if result['flags'] and 'ROLE_BASED' in result['flags']:
            result['status'] = 'RISKY'
            result['reason'] = 'Role-based address'
    except dns.resolver.NoAnswer:
        # Try A record fallback
        try:
            dns.resolver.resolve(domain, 'A')
            result['status'] = 'RISKY'
            result['reason'] = 'A record only, no MX'
            result['flags'].append('NO_MX_A_ONLY')
        except:
            result['status'] = 'INVALID'
            result['reason'] = 'No DNS records'
    except dns.resolver.NXDOMAIN:
        result['status'] = 'INVALID'
        result['reason'] = 'Domain does not exist'
    except Exception as e:
        result['reason'] = f'DNS lookup failed: {str(e)}'

    result['flags'] = '|'.join(result['flags'])
    return result

Performance

  • ~160 emails/sec in DNS-only mode
  • 44K leads in ~5 minutes with --workers 20
  • Per-domain concurrency capped at 2 with 0.5s delay (built-in rate limiting)

Limitation

If port 25 is blocked on your server, the tool falls back to DNS-only mode. This means:

  • No SMTP mailbox existence check (RCPT TO probe)
  • No catch-all domain detection
  • Results are domain-level, not mailbox-level

For full SMTP verification, use an external service (e.g., ZeroBounce, NeverBounce) or run from a server with port 25 open.

Output CSV Columns

ColumnDescription
emailLowercased, trimmed email
statusVALID / INVALID / RISKY / UNKNOWN
reasonHuman-readable explanation
mx_hostMX server hostname (if found)
flagsPipe-separated flags (ROLE_BASED, FREE_PROVIDER, NO_MX_A_ONLY)
checked_atISO 8601 UTC timestamp

Built by PureBrain -- AI-powered marketing operations.

Want the full suite of 183+ production-tested skills? Start your trial ->

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.