mailvalidation / app.py
chandugeesala0's picture
Update app.py
cfcda37 verified
#!/usr/bin/env python3
"""
Email Validator - Hugging Face Spaces (Gradio Version)
Works perfectly on Hugging Face because Gradio is native
"""
import gradio as gr
import socket
import time
import uuid
from typing import Dict, List, Tuple
import dns.resolver
import smtplib
import logging
logging.basicConfig(level=logging.INFO)
FROM_ADDRESS = 'verifier@yourdomain.com'
SMTP_TIMEOUT = 15
RATE_LIMIT_SECONDS = 1.0
# Domain cache
domain_cache = {}
def is_valid_format(email: str) -> bool:
"""Basic email format validation"""
try:
return bool(email and '@' in email and email.count('@') == 1 and '.' in email.split('@')[1])
except:
return False
def resolve_mx_or_a(domain: str):
"""Return list of MX hosts or A records"""
try:
answers = dns.resolver.resolve(domain, 'MX')
mx = sorted([(r.preference, str(r.exchange).rstrip('.')) for r in answers], key=lambda x: x[0])
return [h for _, h in mx]
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.NoNameservers, dns.exception.Timeout):
try:
a = dns.resolver.resolve(domain, 'A')
return [str(r) for r in a]
except Exception:
return []
def generate_random_localpart():
"""Generate random email local part for catch-all detection"""
return 'probe-' + uuid.uuid4().hex[:12]
def smtp_probe_recipient(mx_host: str, from_addr: str, recipient: str):
"""SMTP RCPT TO probe"""
try:
with smtplib.SMTP(host=mx_host, port=25, timeout=SMTP_TIMEOUT) as smtp:
smtp.set_debuglevel(0)
smtp.ehlo_or_helo_if_needed()
if smtp.has_extn('starttls'):
try:
smtp.starttls()
smtp.ehlo()
except Exception:
pass
smtp.mail(from_addr)
rcpt_code, rcpt_msg = smtp.rcpt(recipient)
if isinstance(rcpt_msg, bytes):
rcpt_msg = rcpt_msg.decode(errors='ignore')
return True, int(rcpt_code), str(rcpt_msg), None
except smtplib.SMTPRecipientsRefused as e:
try:
rec = e.recipients.get(recipient)
if rec:
code, resp = rec
return True, int(code), resp.decode() if isinstance(resp, bytes) else str(resp), None
except Exception:
pass
return False, None, None, 'SMTPRecipientsRefused'
except (smtplib.SMTPConnectError, smtplib.SMTPServerDisconnected, socket.timeout, ConnectionRefusedError, OSError):
return False, None, None, 'NETWORK_ERROR'
except Exception:
return False, None, None, 'ERROR'
def decide_outcome(email, mx_host, rcpt_code, catch_all_flag, probe_error):
"""Determine if email is valid or invalid"""
if not is_valid_format(email):
return 'INVALID', 'INVALID_FORMAT'
if not mx_host:
return 'INVALID', 'UNDELIVERABLE_NO_MX'
if probe_error or rcpt_code is None:
return 'INVALID', 'PROBE_FAILED_NETWORK'
if 500 <= rcpt_code <= 599:
return 'INVALID', 'MAILBOX_DOES_NOT_EXIST'
if 200 <= rcpt_code <= 299:
if catch_all_flag:
return 'VALID', 'UNKNOWN_CATCHALL'
else:
return 'VALID', 'MAILBOX_EXISTS'
if 400 <= rcpt_code <= 499:
return 'INVALID', 'TEMPORARY_ERROR'
return 'INVALID', 'UNKNOWN_STATUS'
def validate_email_function(email: str) -> Dict:
"""Validate a single email"""
email = email.strip().lower()
if not is_valid_format(email):
return {
'Email': email,
'Status': '❌ INVALID',
'Reason': 'Invalid format',
'Valid': 'No'
}
domain = email.split('@')[1].lower()
# Resolve MX (cached per domain)
if domain not in domain_cache:
hosts = resolve_mx_or_a(domain)
domain_cache[domain] = {'hosts': hosts, 'catch_all': None}
hosts = domain_cache[domain]['hosts']
if not hosts:
return {
'Email': email,
'Status': '❌ INVALID',
'Reason': 'No MX records',
'Valid': 'No'
}
mx_host = hosts[0]
time.sleep(RATE_LIMIT_SECONDS)
# Catch-all detection (once per domain)
if domain_cache[domain]['catch_all'] is None:
random_local = generate_random_localpart()
random_email = f"{random_local}@{domain}"
ok, code, msg, err = smtp_probe_recipient(mx_host, FROM_ADDRESS, random_email)
catch_all = False
if ok and code and 200 <= code <= 299:
catch_all = True
domain_cache[domain]['catch_all'] = catch_all
time.sleep(RATE_LIMIT_SECONDS)
catch_all_flag = domain_cache[domain]['catch_all']
# Probe actual recipient
ok, code, msg, err = smtp_probe_recipient(mx_host, FROM_ADDRESS, email)
status, reason = decide_outcome(
email=email,
mx_host=mx_host,
rcpt_code=code,
catch_all_flag=catch_all_flag,
probe_error=(not ok or err is not None)
)
is_valid = status == 'VALID'
status_display = '✅ VALID' if is_valid else '❌ INVALID'
valid_text = 'Yes' if is_valid else 'No'
return {
'Email': email,
'Status': status_display,
'Reason': reason,
'Valid': valid_text
}
def validate_batch(emails_text: str) -> str:
"""Validate multiple emails from text input"""
emails = [e.strip() for e in emails_text.split('\n') if e.strip()]
if not emails:
return "Please enter at least one email"
results = []
valid_count = 0
for email in emails:
result = validate_email_function(email)
results.append(result)
if result['Valid'] == 'Yes':
valid_count += 1
# Format output
output = f"**Total: {len(emails)} | Valid: {valid_count} | Invalid: {len(emails) - valid_count}**\n\n"
output += "| Email | Status | Reason | Valid |\n"
output += "|-------|--------|--------|-------|\n"
for r in results:
output += f"| {r['Email']} | {r['Status']} | {r['Reason']} | {r['Valid']} |\n"
return output
# Create Gradio Interface
with gr.Blocks(title="Email Validator") as demo:
gr.Markdown("# 📧 Email Validator API")
gr.Markdown("Validate emails using DNS MX records and SMTP probing")
with gr.Tab("Single Email"):
with gr.Row():
email_input = gr.Textbox(
label="Email Address",
placeholder="Enter email to validate",
value="test@gmail.com"
)
validate_btn = gr.Button("Validate", variant="primary", size="lg")
with gr.Row():
email_output = gr.Textbox(label="Email", interactive=False)
status_output = gr.Textbox(label="Status", interactive=False)
with gr.Row():
reason_output = gr.Textbox(label="Reason", interactive=False)
valid_output = gr.Textbox(label="Valid", interactive=False)
validate_btn.click(
fn=validate_email_function,
inputs=email_input,
outputs=[email_output, status_output, reason_output, valid_output]
)
with gr.Tab("Batch Validation"):
with gr.Row():
batch_input = gr.Textbox(
label="Emails (one per line)",
placeholder="test1@gmail.com\ntest2@yahoo.com\ninvalid@test.com",
lines=10
)
batch_btn = gr.Button("Validate Batch", variant="primary", size="lg")
batch_output = gr.Markdown(label="Results")
batch_btn.click(
fn=validate_batch,
inputs=batch_input,
outputs=batch_output
)
with gr.Tab("API Docs"):
gr.Markdown("""
## API Endpoints
### Single Email Validation
- **URL:** `/api/validate`
- **Method:** POST
- **Payload:** `{"email": "test@gmail.com"}`
### Batch Validation
- **URL:** `/api/batch`
- **Method:** POST
- **Payload:** `{"emails": ["test1@gmail.com", "test2@yahoo.com"]}`
## Response
```
{
"Email": "test@gmail.com",
"Status": "✅ VALID",
"Reason": "MAILBOX_EXISTS",
"Valid": "Yes"
}
```
""")
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)