<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Den</title>
    <description>The latest articles on DEV Community by Den (@densi).</description>
    <link>https://dev.to/densi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3584550%2Fe7a578a4-39a5-4d84-8818-e632c3a592e1.gif</url>
      <title>DEV Community: Den</title>
      <link>https://dev.to/densi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/densi"/>
    <language>en</language>
    <item>
      <title>How to build a Python email-checker using the Mail7 API (and spot disposable addresses like TempMailbox)</title>
      <dc:creator>Den</dc:creator>
      <pubDate>Tue, 28 Oct 2025 09:14:18 +0000</pubDate>
      <link>https://dev.to/densi/how-to-build-a-python-email-checker-using-the-mail7-api-and-spot-disposable-addresses-like-3bh2</link>
      <guid>https://dev.to/densi/how-to-build-a-python-email-checker-using-the-mail7-api-and-spot-disposable-addresses-like-3bh2</guid>
      <description>&lt;p&gt;Want to verify whether an email is real (deliverable) or just a throwaway disposable address (e.g. TempMailbox)? In this post I’ll show the purpose, how to write a small Python script that uses the Mail7 public Email Checker API, and how to use it in simple workflows (single-check, bulk-check, rate-limit handling). Code examples are ready-to-run and easy to adapt for production use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prevent fake or disposable emails polluting your database (improves deliverability &amp;amp; analytics).&lt;/li&gt;
&lt;li&gt;Reduce fraud, reduce bounce rates and protect downstream workflows (password resets, marketing).&lt;/li&gt;
&lt;li&gt;Lightweight: &lt;a href="https://mail7.net/api-docs.html" rel="noopener noreferrer"&gt;Mail7’s API&lt;/a&gt; is public and simple — you don’t need API keys to get started.  ￼&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Example disposable service referenced in this post: &lt;a href="https://tempamailbox.net" rel="noopener noreferrer"&gt;TempMailbox&lt;/a&gt; — a typical disposable/temp-mail provider.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick notes from Mail7 docs (what matters)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base URL&lt;/strong&gt;: &lt;a href="https://mail7.net" rel="noopener noreferrer"&gt;https://mail7.net&lt;/a&gt;. No authentication required — it’s a public API.  ￼&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limit&lt;/strong&gt;: 5 requests / minute / IP. Exceeding it returns HTTP 429 with a Retry-After header. Your script should respect this.  ￼&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key endpoints used here&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;POST /api/validate-single — single email check (JSON body {"email": "..."} ). Response contains valid, formatValid, mxValid, smtpValid, status, details and importantly is_disposable.  ￼&lt;/li&gt;
&lt;li&gt;POST /api/validate-bulk — upload file or text list for bulk checks. Useful for offline cleanup.  ￼&lt;/li&gt;
&lt;li&gt;GET /api/spf-check/{domain} - optional: check domain SPF if you want to add additional heuristics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Python script (complete, annotated)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Save as mail7_check.py. It does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;single-check via /api/validate-single&lt;/li&gt;
&lt;li&gt;handles rate-limit (429 + Retry-After)&lt;/li&gt;
&lt;li&gt;simple backoff on server errors&lt;/li&gt;
&lt;li&gt;can be used interactively or imported as a module
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env python3
"""
mail7_check.py
Simple utilities to check an email address using Mail7 Email Checker API.
"""

import time
import requests
from typing import Dict, Optional

BASE = "https://mail7.net"
SINGLE_ENDPOINT = f"{BASE}/api/validate-single"
BULK_ENDPOINT = f"{BASE}/api/validate-bulk"
SPF_ENDPOINT = f"{BASE}/api/spf-check/{{}}"

# Simple wrapper for single validation
def validate_single(email: str, timeout: float = 10.0) -&amp;gt; Dict:
    """Validate a single email. Returns the parsed JSON response."""
    payload = {"email": email}
    headers = {"Content-Type": "application/json"}
    while True:
        resp = requests.post(SINGLE_ENDPOINT, json=payload, headers=headers, timeout=timeout)
        if resp.status_code == 200:
            return resp.json()
        if resp.status_code == 429:
            # Respect Retry-After header if present
            retry = resp.headers.get("Retry-After")
            wait = int(retry) if retry and retry.isdigit() else 60
            print(f"[rate-limit] 429 received. Waiting {wait} seconds...")
            time.sleep(wait)
            continue
        if 500 &amp;lt;= resp.status_code &amp;lt; 600:
            # transient server error, back off a bit
            print(f"[server-error] {resp.status_code}. Backing off 5s...")
            time.sleep(5)
            continue
        # For other errors, raise
        resp.raise_for_status()

def is_good_email(result: Dict) -&amp;gt; bool:
    """
    Heuristic to decide whether to accept this email:
      - result['valid'] should be True
      - smtpValid and mxValid are helpful
      - is_disposable should be False
      - status normally contains 'Valid' for good addresses
    Adapt this logic to your business needs.
    """
    if not result:
        return False
    if result.get("is_disposable"):
        return False
    # require overall valid + smtp existence
    if result.get("valid") and result.get("smtpValid"):
        return True
    # fallback: format + mx
    if result.get("formatValid") and result.get("mxValid"):
        return True
    return False

def pretty_print(result: Dict):
    print("email:", result.get("email"))
    print("status:", result.get("status"))
    print("valid:", result.get("valid"))
    print("formatValid:", result.get("formatValid"))
    print("mxValid:", result.get("mxValid"))
    print("smtpValid:", result.get("smtpValid"))
    print("is_disposable:", result.get("is_disposable"))
    print("details:", result.get("details"))

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Check email validity using mail7.net API")
    parser.add_argument("email", help="Email to check, or path to file with emails (one per line) if --bulk")
    parser.add_argument("--bulk", action="store_true", help="Treat argument as a file and run bulk check (calls validate-bulk)")
    args = parser.parse_args()

    if args.bulk:
        # Bulk mode: upload file to /api/validate-bulk (form data: 'emails')
        with open(args.email, "rb") as f:
            files = {"emails": (args.email, f)}
            r = requests.post(BULK_ENDPOINT, files=files)
            r.raise_for_status()
            data = r.json()
            print("Total:", data.get("total"))
            for item in data.get("results", []):
                print("----")
                pretty_print(item)
    else:
        res = validate_single(args.email)
        pretty_print(res)
        print("DECISION:", "ACCEPT" if is_good_email(res) else "REJECT")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install requests
python3 mail7_check.py [email to check]
# or bulk:
python3 mail7_check.py emails.txt --bulk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How the script decides disposable vs real
&lt;/h2&gt;

&lt;p&gt;Mail7’s response contains an is_disposable boolean. Use that directly to detect throwaway providers (like TempMailbox). Combine that with smtpValid and mxValid to be confident — smtpValid=true strongly indicates the address exists and accepts mail.  ￼&lt;/p&gt;

&lt;p&gt;Example logic in the script (is_good_email) returns False when is_disposable is true, or when the address fails SMTP checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world usage examples&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signup form — call validate-single on email submission. If is_disposable is true or smtpValid is false, show a friendly error or ask the user for a non-disposable email.&lt;/li&gt;
&lt;li&gt;CRM cleanup — every week, run a bulk pass with validate-bulk and remove invalid/disposable addresses from mailing lists.&lt;/li&gt;
&lt;li&gt;Fraud screening — combine is_disposable with geo/IP/device signals for higher-risk signups.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveats &amp;amp; tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;False negatives/positives: No validation is perfect. Some providers block SMTP probes, some disposable providers forward to real addresses — combine signals (mx, smtp, is_disposable, and business rules).&lt;/li&gt;
&lt;li&gt;Respect privacy &amp;amp; terms: Don’t leak results or perform abusive queries. If you need bulk/enterprise usage, contact Mail7 for a supported arrangement (the docs describe basic public API behavior).&lt;/li&gt;
&lt;li&gt;Disposable services evolve. Lists of disposable domains change — relying solely on a static list is brittle. Using Mail7’s is_disposable is easier because they update their detection on the provider side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final notes &amp;amp; links&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mail7 API docs (use them as the definitive reference for endpoints, payloads and rate limits): &lt;a href="https://mail7.net/api-docs.html" rel="noopener noreferrer"&gt;https://mail7.net/api-docs.html&lt;/a&gt;.  ￼&lt;/li&gt;
&lt;li&gt;Example disposable provider for reference: &lt;a href="https://tempmailbox.net" rel="noopener noreferrer"&gt;https://tempmailbox.net&lt;/a&gt; (TempMailbox) — great example of a disposable mailbox you may want to block on registration.  ￼&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>api</category>
      <category>security</category>
    </item>
  </channel>
</rss>
