Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk5 min

Python Email List Checks: Validate CSVs Without Losing Rows

A cautious Python workflow for checking email CSV syntax and domain DNS signals without claiming to prove mailbox delivery.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Yes—but a Python script can check whether an address is correctly formatted and whether its domain appears able to receive mail; it cannot prove that a particular mailbox exists or will accept your campaign. The workflow below reads a CSV, preserves every row, checks syntax and optionally domain DNS, and writes a separate results file so uncertain cases can be reviewed instead of silently discarded.

What a bulk email check can—and cannot—tell you

Syntax validation catches malformed address forms, such as a missing domain or an invalid character. A DNS/MX lookup can indicate whether the domain has mail-routing records. Neither check confirms that an individual mailbox exists, that its owner consented to receive your email, or that your message will reach the inbox.

As an Amazon Associate I earn from qualifying purchases.

For that reason, the script labels a row syntax_ok, not “deliverable.” A domain-level failure is marked separately, and an unexpected lookup or processing problem becomes review. Those distinctions matter: temporary DNS trouble should not be mistaken for a permanently invalid recipient.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prepare the CSV and install the validator

Use a CSV with a header row and identify the address column explicitly. The example assumes the column is named email; change that value in the script if your header differs. It preserves the original columns and adds a row number, status, reason, and normalized address to a new output file.

Install the maintained email-validator package and its DNS dependency with:

python -m pip install email-validator dnspython

Keep the input file unchanged. The original address is retained in the output for auditing, while the library’s normalized form is used to detect repeated addresses. Normalization is for comparison; it is not a reason to rewrite unrelated contact data.

Run the CSV check

Save this as check_emails.py. It checks syntax and domain deliverability signals, reuses a caching DNS resolver for the batch, and writes results to email_check_results.csv.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import csv
from email_validator import (
    EmailNotValidError,
    EmailSyntaxError,
    EmailUndeliverableError,
    caching_resolver,
    validate_email,
)

INPUT_FILE = "contacts.csv"
OUTPUT_FILE = "email_check_results.csv"
EMAIL_COLUMN = "email"

# Reuse DNS results across rows and bound lookup time.
resolver = caching_resolver(timeout=10)
seen = set()

with open(INPUT_FILE, newline="", encoding="utf-8-sig") as source:
    reader = csv.DictReader(source)
    if not reader.fieldnames or EMAIL_COLUMN not in reader.fieldnames:
        raise ValueError(f"CSV must contain a '{EMAIL_COLUMN}' column")

    fieldnames = list(reader.fieldnames) + [
        "source_row", "check_status", "check_reason", "normalized_email"
    ]

    with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as destination:
        writer = csv.DictWriter(destination, fieldnames=fieldnames)
        writer.writeheader()

        for row_number, row in enumerate(reader, start=2):
            original = row.get(EMAIL_COLUMN) or ""
            address = original.strip()
            row.update({
                "source_row": row_number,
                "check_status": "review",
                "check_reason": "",
                "normalized_email": "",
            })

            if not address:
                row["check_status"] = "syntax_invalid"
                row["check_reason"] = "empty address"
            else:
                try:
                    result = validate_email(
                        address,
                        check_deliverability=True,
                        dns_resolver=resolver,
                    )
                    normalized = result.normalized
                    row["normalized_email"] = normalized
                    if normalized in seen:
                        row["check_status"] = "review"
                        row["check_reason"] = "duplicate normalized address"
                    else:
                        seen.add(normalized)
                        row["check_status"] = "syntax_ok"
                        row["check_reason"] = "syntax valid; domain DNS check passed"
                except EmailSyntaxError as error:
                    row["check_status"] = "syntax_invalid"
                    row["check_reason"] = str(error)
                except EmailUndeliverableError as error:
                    row["check_status"] = "domain_unavailable"
                    row["check_reason"] = str(error)
                except EmailNotValidError as error:
                    row["check_status"] = "review"
                    row["check_reason"] = str(error)
                except Exception as error:
                    # Includes unexpected or temporary DNS/library failures.
                    row["check_status"] = "review"
                    row["check_reason"] = f"lookup or processing issue: {error}"

            writer.writerow(row)

print(f"Wrote {OUTPUT_FILE}")

Interpret the output conservatively

  • syntax_invalid: the address is empty or failed syntax validation. Inspect the reason before correcting or removing it.
  • domain_unavailable: the validator reported a domain-level mail problem. This is not a finding about whether a specific mailbox exists.
  • syntax_ok: syntax passed and the domain DNS check returned a usable signal. This does not establish mailbox existence or campaign delivery.
  • review: the address was duplicated after normalization, or the check raised an inconclusive or unexpected error. Resolve these rows deliberately rather than treating them as invalid.

The row number refers to the CSV record’s line position, beginning with the header on line 1. Keep it, or use an existing stable contact ID, to join results back to other records without relying on email addresses as unique identifiers.

Test a sample and handle uncertain results

Run the script on a copy containing a small sample first. Confirm that the header matches, that every input record appears in the output, and that reasons are understandable. Inspect review and domain_unavailable rows before deciding what to do; DNS can be slow or unreliable, and a transient failure is not proof that an address is bad.

For large batches, the reusable resolver caches repeated DNS lookups and the ten-second timeout bounds each lookup. A timeout or other transient failure should remain a review item, not be converted into a definitive invalid result. If your environment produces DNS exceptions not handled by the library’s validation exceptions, this example records them as review; check the error and retry selectively if appropriate.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Why the script does not probe mailboxes with SMTP

Do not make SMTP mailbox probing the default next step. Python’s smtplib exposes SMTP VRFY, but the Python documentation notes, “Many sites disable SMTP VRFY in order to foil spammers.” Python’s smtplib documentation describes the command; it is not a universal bulk-recipient verification service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Even when a server appears to accept a recipient, that response may be ambiguous or temporary. Privacy protections, greylisting, temporary failures, and delayed bounces can all undermine a probe’s result. The email-validator project documentation explains these limits and why contacting SMTP servers offers little benefit for this purpose.

List cleanup is separate from sending compliance

Verification does not replace permission to contact recipients, a working unsubscribe process, or sender authentication. Google says all senders must set up SPF or DKIM, while bulk senders must set up SPF, DKIM, and DMARC; authentication can reduce rejection or spam classification risk but does not guarantee inbox placement. See Google’s email sender guidelines.

Google’s bulk-sender classification is specific to mail sent to personal Gmail accounts: it applies to senders approaching 5,000 or more messages to those accounts within 24 hours. Google aggregates messages from subdomains under the same primary domain, and says bulk-sender status does not expire once assigned. This is Google’s threshold, not a universal definition of bulk email. Google’s sender-guidelines FAQ says enforcement of non-compliant traffic has been ramping up since November 2025, including temporary and permanent rejections; operational rules can change, so consult the current guidance when planning a Gmail campaign.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.