Skip to content

bug: contact-discovery-service SMTP email verifier opens a real TCP connection to port 25 on discovered domains β€” many corporate mail servers block port-25 probes and flag the source IP,getting the user's IP blacklisted by spam protection servicesΒ #160

Description

@prince-pokharna

πŸ› Problem Statement

Arachnode's contact-discovery-service/verifier.py validates discovered email addresses via SMTP checks. This involves:

  1. Opening a TCP connection to the discovered domain's MX server on port 25
  2. Sending EHLO and RCPT TO commands to probe if the mailbox exists

This pattern has several serious real-world problems:

  • IP Blacklisting: Corporate mail servers (Google Workspace, Microsoft 365, Outlook) actively log and block hosts that probe port 25 without sending mail. After ~50 probes, the user's home/VPS IP can be flagged by Spamhaus or similar services
  • False Verification: Most modern mail servers return 250 OK for any RCPT TO regardless of whether the mailbox exists (to prevent harvesting) β€” making the verification useless
  • Latency: Each SMTP probe takes 2–5 seconds, making contact discovery extremely slow for large company lists

πŸ’‘ Proposed Fix

1. Replace SMTP probing with MX record + disposable domain check (non-intrusive)

# contact-discovery-service/verifier.py

import dns.resolver
import re

DISPOSABLE_DOMAINS = {
    'mailinator.com', 'guerrillamail.com', 'tempmail.com', 'throwaway.email'
}

def verify_email(email: str) -> str:
    """
    Returns: 'verified' | 'unverifiable' | 'invalid' | 'disposable'
    Does NOT connect to the target mail server.
    """
    pattern = r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$'
    if not re.match(pattern, email):
        return 'invalid'

    domain = email.split('@')[1].lower()

    if domain in DISPOSABLE_DOMAINS:
        return 'disposable'

    # Check MX record exists β€” confirms domain accepts mail at all
    try:
        mx_records = dns.resolver.resolve(domain, 'MX')
        if mx_records:
            return 'verified'  # MX exists β€” best we can do non-intrusively
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.exception.DNSException):
        return 'invalid'

    return 'unverifiable'

2. Add a rate limit on contact discovery per domain

# contact-discovery-service/main.py

from slowapi import Limiter
limiter = Limiter(key_func=lambda req: req.query_params.get("company", "global"))

@app.post("/discover")
@limiter.limit("5/minute")  # Max 5 discovery requests per company per minute
async def discover_contacts(request: Request, payload: DiscoverPayload):
    ...

πŸ“ Files to Modify

File Change
contact-discovery-service/verifier.py Replace SMTP probe with DNS MX + regex validation
contact-discovery-service/requirements.txt Replace smtplib usage; add dnspython
README.md β€” Ethics section Document non-intrusive verification approach

Suggested labels: bug, contact-discovery, security

I would like to work on this. Could you please assign it to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions