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.
Why this is useful
- Prevent fake or disposable emails polluting your database (improves deliverability & analytics).
- Reduce fraud, reduce bounce rates and protect downstream workflows (password resets, marketing).
- Lightweight: Mail7’s API is public and simple — you don’t need API keys to get started. 
(Example disposable service referenced in this post: TempMailbox — a typical disposable/temp-mail provider.)
Quick notes from Mail7 docs (what matters)
- Base URL: https://mail7.net. No authentication required — it’s a public API. 
- Rate limit: 5 requests / minute / IP. Exceeding it returns HTTP 429 with a Retry-After header. Your script should respect this. 
- Key endpoints used here:
- POST /api/validate-single — single email check (JSON body {"email": "..."} ). Response contains valid, formatValid, mxValid, smtpValid, status, details and importantly is_disposable. 
- POST /api/validate-bulk — upload file or text list for bulk checks. Useful for offline cleanup. 
- GET /api/spf-check/{domain} - optional: check domain SPF if you want to add additional heuristics.
The Python script (complete, annotated)
Save as mail7_check.py. It does:
- single-check via /api/validate-single
- handles rate-limit (429 + Retry-After)
- simple backoff on server errors
- can be used interactively or imported as a module
#!/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) -> 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 <= resp.status_code < 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) -> 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")
Requirements
pip install requests
python3 mail7_check.py [email to check]
# or bulk:
python3 mail7_check.py emails.txt --bulk
How the script decides disposable vs real
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. 
Example logic in the script (is_good_email) returns False when is_disposable is true, or when the address fails SMTP checks.
Real-world usage examples
- 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.
- CRM cleanup — every week, run a bulk pass with validate-bulk and remove invalid/disposable addresses from mailing lists.
- Fraud screening — combine is_disposable with geo/IP/device signals for higher-risk signups.
Caveats & tips
- 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).
- Respect privacy & 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).
- 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.
Final notes & links
- Mail7 API docs (use them as the definitive reference for endpoints, payloads and rate limits): https://mail7.net/api-docs.html. 
- Example disposable provider for reference: https://tempmailbox.net (TempMailbox) — great example of a disposable mailbox you may want to block on registration. 
Top comments (0)