1. Home
  2. API docs
  3. Flask

Validate email addresses in Flask

To validate an email in Flask, call the SimpleVerifier API from your route or a Flask-WTF validator and reject the address only when the status is invalid. Keep the key in an environment variable, run a format check before the API call, and accept the address if the request times out.

By SimpleVerifier Team

Before you start

  • Create a key on the API keys page. Keys start with sv_live_ and are shown once.
  • Set SIMPLEVERIFIER_KEY in the environment your WSGI server runs in (systemd unit, Docker env, or your platform's settings).
  • API access is included in the Pro plan ($29.99/month) and the Email + Phone plan ($49.99/month). See pricing.

1. Add the helper

This is the same helper as the Python guide. Save it next to your app and install requests.

simpleverifier.py
# simpleverifier.py — Python 3.10+, needs `requests`
import logging
import os
import re

import requests

API_URL = "https://simpleverifier.com/api/v1/verify"
EMAIL_SHAPE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
log = logging.getLogger(__name__)


def email_status(email: str) -> str | None:
    """Return "valid", "risky" or "invalid", or None if the API could not answer."""
    email = email.strip()
    if not EMAIL_SHAPE.match(email):
        return "invalid"  # fails the local syntax check, no API call made

    try:
        r = requests.get(
            API_URL,
            params={"email": email},
            headers={"Authorization": f"Bearer {os.environ['SIMPLEVERIFIER_KEY']}"},
            timeout=(5, 30),  # connect, read
        )
    except requests.RequestException as exc:
        log.warning("SimpleVerifier unreachable: %s", exc)
        return None

    if r.status_code != 200:
        log.warning("SimpleVerifier returned HTTP %s", r.status_code)
        return None  # 401, 402, 429, 503: fail open

    return r.json().get("status")

2. Check the address in a route

Return 422 for an undeliverable address so your front end can show the message next to the field.

app.py
# app.py — Flask 2.0+; simpleverifier.py is the helper shown above
from flask import Flask, jsonify, request

from simpleverifier import email_status

app = Flask(__name__)


@app.post("/signup")
def signup():
    email = (request.form.get("email") or "").strip()
    status = email_status(email)

    if status == "invalid":
        return jsonify(error="This email address cannot receive mail. Please check it."), 422

    # status is "valid", "risky" or None (API unavailable): accept the signup.
    # Save it here, keeping status if you want to review risky addresses later.
    return jsonify(ok=True, email_status=status), 201

3. Or as a Flask-WTF validator

WTForms runs every validator in the list and collects errors, so the custom validator checks field.errors and skips the API call when the format check already failed.

forms.py
# forms.py — Flask-WTF (the Email() validator needs the email-validator package)
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired, Email, ValidationError

from simpleverifier import email_status


def deliverable(form, field):
    if field.errors:
        return  # Email() already rejected the syntax, so skip the API call
    if email_status(field.data) == "invalid":
        raise ValidationError("This email address cannot receive mail. Please check it.")


class SignupForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired(), Email(), deliverable])

What the API returns

GET or POST https://simpleverifier.com/api/v1/verify with email runs the syntax, domain, disposable-domain and live mailbox checks in one call and returns:

{
  "email": "jane@company.com",
  "status": "valid",
  "reason": "Mailbox exists",
  "mailbox_checked": true
}

Every snippet above follows the same rule: only a successful response with status set to invalid blocks a person.

ResultWhat it meansWhat to do
validThe mail server confirmed the mailbox exists.Accept.
riskyThe domain is real but the mailbox couldn't be confirmed: catch-all domains, role addresses like info@, providers that hide mailbox status.Accept on signup forms. Store the status and keep these out of cold campaigns.
invalidBad format, no mail server, a disposable domain, or the mailbox doesn't exist.Reject and ask the person to check the address.
timeout, 429 or 503The check didn't finish: a slow mail server, too many calls this minute, or a brief outage.Accept (fail open) and log it. Re-check later if it matters.
401 or 402The key is wrong or revoked, or the subscription isn't active.Accept, and alert yourself: every check is being skipped until it's fixed.

Errors return JSON with an error message. Each key can make 120 calls a minute; for thousands of addresses, upload a CSV in the dashboard instead.

Phone numbers and spreadsheets

The same key works with /api/v1/phone?phone=…&country=… to validate a phone number and normalise it to E.164, with live carrier lookups on the Email + Phone plan. The request and response are in the API reference. To check a column of addresses without code, use the Google Sheets add-on with the same key.

Common questions

Does this work with Quart or async Flask views?
The helper uses requests, which blocks. In an async view, either run it with asyncio.to_thread(email_status, email) or swap requests for httpx.AsyncClient with the same URL, header and timeout.
Where should I store the result?
Saving the status next to the user (valid, risky or empty) is useful later: you can leave risky addresses out of cold campaigns, and re-check the empty ones when the API is back.
How much does the SimpleVerifier API cost?
API access is included in the paid plans: Pro at $29.99/month and Email + Phone at $49.99/month. There are no per-call credits. Each key can make 120 calls a minute.
Should I reject risky addresses?
Not on a signup or contact form. Risky means the mailbox couldn't be confirmed, which includes every address at a catch-all company domain. Accept it, store the status, and leave risky addresses out of cold outreach.
What if the API is slow or down?
Let the address through. A mailbox check can take several seconds when a mail server is slow, so cap the wait at about 30 seconds (less on a busy form) and treat a timeout, a 429 or a 503 as unknown rather than invalid.

Ready to clean your email and phone lists?

Start verifying emails and phone numbers today and boost your deliverability.

Get Started

Cancel anytime • No setup required