A self-hosted, multi-layer email verification engine built with Flask.
Built to replace expensive paid services like ZeroBounce or NeverBounce for personal, freelance, and automation workflows.
This API checks every email through 4 distinct layers before it ever reaches your sending queue, protecting your SMTP server and domain reputation. It returns valid, risky, or invalid with a specific reason for each email.
| Layer | Verification Check | Example Catch | Action |
|---|---|---|---|
| 1 | Syntax Validation | user@ |
❌ Invalid |
| 2 | Disposable Domain | mailinator.com |
❌ Invalid |
| 3 | Role-Based Prefix | info@, admin@ |
|
| 4 | Live MX + SMTP Check | Non-existent mailbox | ❌ Invalid |
├── verify-app.py # Flask API — core verification logic
├── index.html # Browser UI — drag-and-drop CSV uploader
├── requirements.txt # Python dependencies
├── Procfile # Railway deployment config
└── README.md # Documentation
1. Clone the repository
git clone https://github.com/ubachan/Email-Verifier-API.git
cd Email-Verifier-API2. Create a virtual environment
python3 -m venv venv
source venv/bin/activate # Mac/Linux
venv\Scripts\activate # Windows3. Install dependencies
pip install -r requirements.txt4. Run the API Engine
python verify-app.py
# → API Running on http://localhost:50505. Open the UI (in a separate terminal)
python3 -m http.server 3000
# → Open http://localhost:3000/index.html in your browserPOST /verify
Upload a CSV file for bulk verification. The CSV must have an email column. Other columns are preserved in the final output.
Request:
curl -X POST http://localhost:5050/verify \
-F "file=@leads.csv"Response:
{
"job_id": "abc-123-def"
}GET /progress?job_id=<id>
{
"percent": 72,
"row": 72,
"total": 100
}GET /download?job_id=<id>&type=<filter>
Download the verified results as a CSV file.
type Parameter |
Returns |
|---|---|
all |
Every row with status + reason |
valid |
Valid emails only |
risky |
Risky emails only |
risky_invalid |
Risky + Invalid combined |
Request:
curl "http://localhost:5050/download?job_id=abc-123&type=valid" -o valid-leads.csvPOST /cancel?job_id=<id>
Stops a currently running verification process.
Deploy this API to the cloud in 3 minutes to use it as a webhook endpoint.
- Push this repository to your GitHub.
- Go to Railway.app → New Project → Deploy from GitHub repo.
- Select this repository. Railway auto-detects the
Procfile. - You get a permanent public URL (e.g.,
https://your-app.up.railway.app).
⚠️ Critical — Update before deploying:Change the last line of
verify-app.pyso it binds to Railway's dynamic port:# REMOVE THIS: # app.run(debug=True, port=5050) # REPLACE WITH THIS: if __name__ == '__main__':
import os
app.run(debug=False, host='0.0.0.0', port=int(os.environ.get('PORT', 5050)))
After deploying to Railway, seamlessly plug this API into your backend automation workflows using the HTTP Request node.
💡 Looking for a ready-made n8n template?
I have built a complete, enterprise-grade automation workflow that uses this exact API alongside OpenAI, Supabase, and Slack.
👉 Get the Advance AI-Powered Lead Management System here
If you are building your workflow from scratch, set up your n8n HTTP Request node like this:
- Method:
POST - URL:
https://your-app.up.railway.app/check(Use/verifyfor CSV files,/checkfor single JSON emails) - Body Type:
JSON(orForm-Datafor files)
Recommended High-Performance Flow:
[ Code Node: Syntax + Disposable + Role Checks ]
↓
[ HTTP Request: Google DNS API (Fast MX Check) ]
↓
[ HTTP Request: THIS API (Deep SMTP Check) ]
↓
[ Switch/If Node: Route to Valid / Invalid ]
Primary Statuses
| Status | Meaning | Recommended Action |
|---|---|---|
🟢 valid |
Passed all 4 layers of checks | Safe to send |
🟡 risky |
Role-based or SMTP timeout | Send with caution |
🔴 invalid |
Bad syntax, fake domain, or rejected | Do not send |
Deep Reason Codes
| Reason | What happened under the hood |
|---|---|
bad_syntax |
Not a valid email format |
disposable_domain |
Temporary/Trash email service detected |
role_based |
Generic prefix used (e.g., info@, admin@, support@) |
no_mx |
The domain has no active email server |
smtp_ok |
The receiving server confirmed the mailbox exists |
smtp_reject |
The receiving server explicitly rejected the email (550) |
smtp_timeout |
The receiving server took too long to respond |
domain_accepts_all |
Catch-all domain — cannot verify individual mailbox |
- API Server: Python 3 + Flask
- Resolution: dnspython (MX record routing)
- Verification: smtplib (SMTP-level mailbox pinging)
- Frontend: Vanilla JS (Browser UI with drag-and-drop & live progress)
Personal Use License — Free to use, modify, distribute, and deploy for personal and educational projects only. Commercial use is strictly prohibited.