The Problem: Your Signup Form Is Bleeding Money
Every fake email that slips through your signup form costs you three ways:
- Direct cost: You're paying your ESP (SendGrid, Mailgun, AWS SES) to send to addresses that bounce.
- Reputation cost: Bounce rates above 2% trigger spam filters at Gmail, Outlook, and Yahoo. Once your domain reputation drops, even valid subscribers stop seeing your emails.
- Data cost: Fake users pollute your analytics, inflate your MAU numbers, and make cohort analysis meaningless.
The solution is dead simple: verify every email before it enters your database. But most developers either skip verification entirely or implement a naive regex check that catches only obvious typos.
This guide shows you how to integrate MailValid's real, documented API in under 10 minutes — with production-ready code, caching, retries, and webhook handling.
MailValid Pricing
| Plan | Monthly Price | Credits | Per-Email Cost |
|---|---|---|---|
| Free | $0 | 100 (one-time) | — |
| Starter | $15/mo | 10,000 | $0.0015 |
| Growth | $50/mo | 50,000 | $0.001 |
| Scale | $75/mo | 100,000+ | $0.00075 |
All plans include syntax, MX, SMTP-level, disposable, catch-all, and role-based checks in a single API call. Credits never expire.
Start with 100 free verifications →
Quick Start: Verify an Email
MailValid's API uses a single endpoint for all verification types. One call gives you syntax, MX, SMTP, disposable, catch-all, and role-based checks.
Python
import requests
API_KEY = "your_mailvalid_api_key"
EMAIL = "user@example.com"
response = requests.post(
"https://mailvalid.io/api/v1/verify/single",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
json={"email": EMAIL},
timeout=10
)
result = response.json()["result"]
print(f"Status: {result['status']}") # valid, invalid, catch_all, unknown, do_not_mail
print(f"Confidence: {result['confidence_score']}") # 0-100
print(f"Disposable: {result['is_disposable']}")
print(f"Free provider: {result['is_free_provider']}")
Node.js
const response = await fetch('https://mailvalid.io/api/v1/verify/single', {
method: 'POST',
headers: {
'X-API-Key': process.env.MAILVALID_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: 'user@example.com' })
});
const { result } = await response.json();
console.log(result.status); // valid | invalid | catch_all | unknown | do_not_mail
console.log(result.confidence_score); // 0-100
cURL
curl -X POST https://mailvalid.io/api/v1/verify/single \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'
Documented response:
{
"success": true,
"credits_used": 1,
"result": {
"email": "user@example.com",
"status": "valid",
"is_valid": true,
"syntax_valid": true,
"domain": "example.com",
"domain_valid": true,
"has_mx": true,
"mx_records": [{"priority": 10, "host": "mail.example.com"}],
"smtp_checked": true,
"smtp_response_code": 250,
"is_disposable": false,
"is_role_based": false,
"is_catch_all": false,
"is_free_provider": true,
"confidence_score": 95,
"status_reason": "mailbox_confirmed",
"provider": "google",
"verification_time_ms": 234,
"cached": false
}
}
Production Integration: Signup Form Pipeline
The quick-start examples above are fine for testing. In production, you need caching, retries, and graceful degradation.
Step 1: Pre-Validation (Client-Side)
Don't waste API calls on obviously invalid emails. Check syntax in the browser first.
function preValidate(email) {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(email) || email.length > 254) {
return { valid: false, reason: 'invalid_syntax' };
}
const [local] = email.split('@');
if (local.length > 64) {
return { valid: false, reason: 'local_part_too_long' };
}
return { valid: true };
}
Step 2: Server-Side Verification with Caching
Cache results to avoid re-verifying the same email repeatedly. Note MailValid's own API also caches recent results server-side and charges 0 credits for cached: true repeats.
import requests
import hashlib
import json
import redis
class EmailVerifier:
def __init__(self, api_key: str, cache_ttl: int = 86400):
self.api_key = api_key
self.cache_ttl = cache_ttl
self.cache = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
self.base_url = "https://mailvalid.io/api/v1"
def _cache_key(self, email: str) -> str:
return f"ev:{hashlib.sha256(email.lower().encode()).hexdigest()[:16]}"
def verify(self, email: str, fresh: bool = False) -> dict:
email = email.lower().strip()
cache_key = self._cache_key(email)
if not fresh:
cached = self.cache.get(cache_key)
if cached:
return json.loads(cached)
response = requests.post(
f"{self.base_url}/verify/single",
headers={
"X-API-Key": self.api_key,
"Content-Type": "application/json"
},
json={"email": email},
timeout=10
)
if response.status_code == 429:
raise Exception("Rate limit exceeded — implement exponential backoff")
result = response.json()["result"]
self.cache.setex(cache_key, self.cache_ttl, json.dumps(result))
return result
def should_accept(self, email: str) -> tuple[bool, str]:
result = self.verify(email)
if not result["is_valid"]:
return False, result["status_reason"]
if result["is_disposable"]:
return False, "disposable_email"
if result["confidence_score"] < 50:
return False, "low_confidence"
return True, "accepted"
Step 3: Retry Logic with Exponential Backoff
Network failures happen. Don't lose a signup because of a transient error.
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session(api_key: str):
session = requests.Session()
session.headers.update({"X-API-Key": api_key})
retry = Retry(
total=5,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["POST"]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("https://", adapter)
return session
Step 4: Real-Time Form Validation (JavaScript)
class EmailVerifier {
constructor(apiKey) {
this.apiKey = apiKey;
this.timer = null;
this.cache = new Map();
}
async verify(email, onResult) {
clearTimeout(this.timer);
if (!this.preValidate(email)) {
onResult({ status: 'invalid', reason: 'syntax_error' });
return;
}
if (this.cache.has(email)) {
onResult(this.cache.get(email));
return;
}
this.timer = setTimeout(async () => {
try {
const res = await fetch('https://mailvalid.io/api/v1/verify/single', {
method: 'POST',
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
const { result } = await res.json();
this.cache.set(email, result);
setTimeout(() => this.cache.delete(email), 5 * 60 * 1000);
onResult(result);
} catch (err) {
onResult({ status: 'unknown', reason: 'api_error' });
}
}, 500);
}
preValidate(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && email.length <= 254;
}
}
const verifier = new EmailVerifier('your_api_key');
document.getElementById('email').addEventListener('blur', (e) => {
verifier.verify(e.target.value, (result) => {
if (result.status === 'valid') {
showSuccess('Valid email address');
} else if (result.status === 'catch_all' || result.status === 'unknown') {
showWarning('This email may not receive messages');
} else {
showError('Please enter a valid email address');
}
});
});
Bulk Verification: Clean a List in One Call
For list cleaning before campaigns, use the bulk endpoint (up to 10,000 emails per request) instead of verifying one by one.
import time
import requests
response = requests.post(
"https://mailvalid.io/api/v1/verify/bulk",
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
json={
"emails": ["user1@example.com", "user2@test.com"], # up to 10,000
"webhook_url": "https://yourapp.com/webhooks/verification"
}
)
job = response.json()
print(f"Job ID: {job['job_id']}, Status: {job['status']}")
while True:
status = requests.get(
f"https://mailvalid.io/api/v1/verify/bulk/{job['job_id']}",
headers={"X-API-Key": api_key}
).json()
if status["status"] == "completed":
valid = [r for r in status["results"] if r["status"] == "valid"]
invalid = [r for r in status["results"] if r["status"] == "invalid"]
print(f"Done: {len(valid)} valid, {len(invalid)} invalid")
break
time.sleep(5)
Webhook Integration: Get Results Without Polling
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret" # from your dashboard or GET /api/auth/stats
@app.route('/webhooks/verification', methods=['POST'])
def handle_webhook():
raw = request.get_data() # sign the raw bytes, do not use request.json
expected = hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
signature = request.headers.get('X-Mailvalid-Signature', '')
if not hmac.compare_digest(expected, signature):
return "Invalid signature", 401
payload = request.json
if payload["event"] == "job.completed":
for result in payload["data"].get("results", []):
if result["status"] == "invalid":
add_to_suppression_list(result["email"])
elif result["status"] == "valid":
mark_as_verified(result["email"])
return jsonify({"received": True}), 200
Common Integration Mistakes (And How to Avoid Them)
Mistake 1: Verifying at Send Time Instead of Capture Time
Better approach: Verify once at signup, then cache the result. Only re-verify if the email later bounces or your list has aged significantly.
Mistake 2: Blocking All "Catch-All" or "Unknown" Emails
A catch-all or unknown result isn't always bad — it means the mailbox couldn't be confirmed, not that it's invalid. Flag these for monitoring rather than auto-rejecting.
if result["status"] in ("catch_all", "unknown"):
# Allow signup but flag for review
user.flags.append("unconfirmed_email")
user.save()
Mistake 3: Ignoring Role Accounts
Role accounts (info@, support@, admin@) are shared mailboxes with low engagement. Filter them out for cold email campaigns, but they're fine for transactional emails.
if result["is_role_based"] and campaign_type == "cold_outreach":
return False, "role_based_address"
Mistake 4: No Fallback on API Failure
If your verification API goes down, your signup form shouldn't break. Implement a fail-open strategy:
try:
result = verifier.verify(email)
except Exception:
user.flags.append("unverified_email")
return True, "api_unavailable"
Conclusion: Start Verifying Today
Email verification isn't optional infrastructure — it's profit protection. Every invalid email you block at signup saves you ESP costs, protects your sender reputation, and keeps your analytics clean.
MailValid gets you started fast:
- From $0.0006 to $0.0015 per email depending on plan
- 100 free verifications, no credit card required
- Single endpoint — one call gets you syntax, MX, SMTP, disposable, catch-all, and role-based detection
- 99.9% uptime SLA
curl -X POST https://mailvalid.io/api/v1/verify/single \
-H "X-API-Key: your_free_key" \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'
Last Updated - 21 September 2026
MailValid Team
Email verification experts
Join teams that verify before they send
Stop letting bad emails hurt your deliverability
100 free credits. From $0.0008/email after. Credits never expire. No credit card required.