Verify email addresses on WordPress forms
To verify emails in WordPress, add a small must-use plugin that calls the SimpleVerifier API with wp_remote_get() and hooks into registration_errors, woocommerce_registration_errors or Contact Form 7's wpcf7_validate_email* filter. Reject the address only when the API says invalid, and let it through if the request fails.
By SimpleVerifier Team
Before you start
- Create a key on the API keys page. Keys start with
sv_live_and are shown once. - Define
SIMPLEVERIFIER_KEYinwp-config.php, read from the server environment, so the key is not stored in the database or in a theme file. - 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 as a must-use plugin
Save this as a file in wp-content/mu-plugins/ (create the folder if it doesn't exist). Must-use plugins load automatically and can't be switched off by accident. Results are cached for a day so a resubmitted form doesn't call the API twice.
<?php
/**
* Plugin Name: SimpleVerifier email check
* Description: Rejects undeliverable addresses on registration and contact forms.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Returns "valid", "risky" or "invalid", or null when the API could not answer.
*/
function sv_email_status( $email ) {
$email = trim( (string) $email );
if ( ! is_email( $email ) ) {
return 'invalid';
}
if ( ! defined( 'SIMPLEVERIFIER_KEY' ) || ! SIMPLEVERIFIER_KEY ) {
return null;
}
$cache_key = 'sv_' . md5( strtolower( $email ) );
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
$response = wp_remote_get(
add_query_arg( 'email', rawurlencode( $email ), 'https://simpleverifier.com/api/v1/verify' ),
array(
'timeout' => 30,
'headers' => array( 'Authorization' => 'Bearer ' . SIMPLEVERIFIER_KEY ),
)
);
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return null; // timeout, rate limit or outage: let the address through
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$status = isset( $data['status'] ) ? $data['status'] : null;
if ( $status ) {
set_transient( $cache_key, $status, DAY_IN_SECONDS );
}
return $status;
}2. Set the key
If your host can't set environment variables, put the key string in place of getenv(). Keep wp-config.php out of version control either way.
<?php
// wp-config.php — above the "That's all, stop editing!" line
define( 'SIMPLEVERIFIER_KEY', getenv( 'SIMPLEVERIFIER_KEY' ) );3. Hook into registration
Both filters receive the error object and the submitted email. Add an error and WordPress shows it above the form. Append these to the same mu-plugin file.
<?php
// Core WordPress registration (wp-login.php?action=register)
add_filter(
'registration_errors',
function ( $errors, $sanitized_user_login, $user_email ) {
if ( 'invalid' === sv_email_status( $user_email ) ) {
$errors->add( 'undeliverable_email', '<strong>Error:</strong> This email address cannot receive mail. Please check it.' );
}
return $errors;
},
10,
3
);
// WooCommerce "My account" registration
add_filter(
'woocommerce_registration_errors',
function ( $errors, $username, $email ) {
if ( 'invalid' === sv_email_status( $email ) ) {
$errors->add( 'undeliverable_email', 'This email address cannot receive mail. Please check it.' );
}
return $errors;
},
10,
3
);4. Hook into Contact Form 7
Contact Form 7 runs a validation filter per field type. This one covers required email fields; add the same function to wpcf7_validate_email for optional ones.
<?php
// Contact Form 7: runs on every required email field ([email* your-email])
add_filter(
'wpcf7_validate_email*',
function ( $result, $tag ) {
$value = isset( $_POST[ $tag->name ] ) ? sanitize_email( wp_unslash( $_POST[ $tag->name ] ) ) : '';
if ( '' !== $value && 'invalid' === sv_email_status( $value ) ) {
$result->invalidate( $tag, 'This email address cannot receive mail. Please check it.' );
}
return $result;
},
20,
2
);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.
| Result | What it means | What to do |
|---|---|---|
| valid | The mail server confirmed the mailbox exists. | Accept. |
| risky | The 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. |
| invalid | Bad 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 503 | The 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 402 | The 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 other form plugins?
- Yes. The helper is plain WordPress code, so any form plugin with a server-side validation hook can call sv_email_status() and show an error when it returns invalid. Gravity Forms, WPForms and Fluent Forms each document their own validation filter.
- Will it block real customers who use a catch-all company domain?
- No. Catch-all domains accept mail for any address, so the mailbox can't be confirmed and the API returns risky, not invalid. The hooks above only reject invalid, so those customers get through.
- 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.