Integration · WooCommerce

WooCommerce email verification at checkout

A typo in the billing email means no order confirmation, no shipping updates and a support ticket later. This plugin checks the address while the customer is still on the checkout page, in the classic checkout and in the Checkout block.

Updated October 10, 2026 · 6 min read

WooCommerce email verification at checkout catches the addresses that can’t receive your order emails: a typo in the domain, a company domain without a mail server, a mailbox that was closed. WooCommerce checks that the billing email is well formed (the classic checkout uses WordPress’s is_email()), but it doesn’t ask the mail server whether the mailbox exists. The plugin below does, with one call to our email validation API when the customer clicks Place order.

What you get

Classic checkout or Checkout block?

WooCommerce has two checkouts, and they fire different hooks:

CheckoutUsed byValidation hook
Classic checkoutThe [woocommerce_checkout] shortcode; stores set up before WooCommerce 8.3, unless they switchedwoocommerce_after_checkout_validation
Checkout block (Store API)Default for new stores since WooCommerce 8.3woocommerce_checkout_validate_order_before_payment (WooCommerce 9.9+)

WooCommerce’s own snippet documentation says that shortcode hooks such as woocommerce_after_checkout_validation don’t run in the Checkout block. The plugin registers both hooks, so it works whichever checkout your store uses. To see which one you have, edit the Checkout page: the classic checkout shows up as a “Classic Shortcode” placeholder block.

WooCommerce checkout email validation: setup in steps

  1. Create an API key in your emailvalidation.io account.

  2. Add the key to wp-config.php, above the line that says “That’s all, stop editing!”. Keeping it there, outside the plugin, means it isn’t in your theme, your database or version control:

    define( 'EMAILVALIDATION_API_KEY', 'your-api-key' );
    
  3. Create the folder wp-content/plugins/evio-checkout-email/ and save the code below as evio-checkout-email.php in it. (A code snippets plugin works too; then leave out the opening <?php line and the comment header.)

  4. In the WordPress admin, go to Plugins and activate “Checkout email check (emailvalidation.io)”.

  5. Place a test order with an address on a domain that doesn’t exist. The checkout should show the error and keep the cart.

<?php
/**
 * Plugin Name: Checkout email check (emailvalidation.io)
 * Description: Rejects undeliverable billing email addresses at checkout.
 */

defined( 'ABSPATH' ) || exit;

/**
 * Checks one address with GET /v1/info. Returns the API result, or null when
 * there is no usable answer (no key, network error, quota, timeout).
 */
function evio_wc_check_email( string $email ): ?array {
	if ( ! defined( 'EMAILVALIDATION_API_KEY' ) ) {
		return null;
	}

	// A customer may submit twice; don't pay twice for the same address.
	$cache_key = 'evio_' . md5( strtolower( $email ) );
	$cached    = get_transient( $cache_key );
	if ( is_array( $cached ) ) {
		return $cached;
	}

	$response = wp_remote_get(
		add_query_arg( 'email', rawurlencode( $email ), 'https://api.emailvalidation.io/v1/info' ),
		array(
			'timeout' => 10,
			'headers' => array( 'apikey' => EMAILVALIDATION_API_KEY ),
		)
	);

	if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
		return null;
	}

	$result = json_decode( wp_remote_retrieve_body( $response ), true );
	if ( ! is_array( $result ) ) {
		return null;
	}

	set_transient( $cache_key, $result, DAY_IN_SECONDS );
	return $result;
}

/**
 * Returns an error message for an undeliverable address, or null to accept it.
 */
function evio_wc_email_error( string $email ): ?string {
	$result = evio_wc_check_email( $email );

	// No answer from the API: let the order through rather than lose it.
	if ( null === $result || 'undeliverable' !== ( $result['state'] ?? '' ) ) {
		return null;
	}

	$message = __( 'We can’t deliver email to this address. Please check it for typos.', 'evio' );
	if ( ! empty( $result['did_you_mean'] ) ) {
		/* translators: %s: suggested email address */
		$message .= ' ' . sprintf( __( 'Did you mean %s?', 'evio' ), esc_html( $result['did_you_mean'] ) );
	}
	return $message;
}

// Classic checkout (the [woocommerce_checkout] shortcode).
add_action(
	'woocommerce_after_checkout_validation',
	function ( array $data, WP_Error $errors ) {
		if ( $errors->has_errors() || empty( $data['billing_email'] ) ) {
			return; // Fix the other errors first; don't spend a credit yet.
		}
		$message = evio_wc_email_error( $data['billing_email'] );
		if ( $message ) {
			$errors->add( 'evio_billing_email', $message );
		}
	},
	10,
	2
);

// Checkout block (Store API, WooCommerce 9.9 or later).
add_action(
	'woocommerce_checkout_validate_order_before_payment',
	function ( WC_Order $order, WP_Error $errors ) {
		$email = $order->get_billing_email();
		if ( ! $email ) {
			return;
		}
		$message = evio_wc_email_error( $email );
		if ( $message ) {
			$errors->add( 'evio_billing_email', $message );
		}
	},
	10,
	2
);

How the two hooks report errors: in the classic checkout, every message added to $errors appears in the checkout notices. In the Checkout block, WooCommerce turns the messages into a woocommerce_rest_checkout_custom_validation_error response with HTTP status 400, which the block shows above the form.

To also refuse disposable addresses, add a check for ! empty( $result['disposable'] ) in evio_wc_email_error(). Most stores don’t: a disposable address still receives the order confirmation, at least for a while. The disposable email checker shows what gets flagged.

Example workflow

  1. A customer types anna@northwind-tradres.example, with a typo in the company domain, and clicks Place order.
  2. WooCommerce runs its own checks (required fields, address format). In the classic checkout, the plugin skips the API call if any of them failed, so you don’t pay for a check on a form that won’t submit anyway.
  3. The plugin calls GET /v1/info. The domain doesn’t exist, so the answer is undeliverable.
  4. The checkout shows “We can’t deliver email to this address. Please check it for typos.” The customer fixes the typo and places the order.
  5. The corrected address is checked once, cached for a day, and the order goes through.

For the customer emails already in your store, export them to CSV and use the bulk email verifier (up to 50 addresses per run) or the bulk endpoint of the API for larger lists. Zapier is an option for after-order checks: its WooCommerce triggers (for example Order and Customer) work with the Emailvalidation action Verify E-Mail Address, but Zapier says this connection requires a paid WooCommerce extension, and it can’t stop an order that was already placed.

The same pattern works in any PHP form handler; the PHP email validation guide shows it with cURL and Laravel.

Pricing note

One check costs 1 credit, and only successful calls count, so a failed request or a validation error costs nothing. In the classic checkout, the plugin calls the API only after WooCommerce’s own checks pass, and it caches each answer for a day. The free plan includes 100 credits a month and allows 10 requests a minute; paid plans have no per-minute limit and start at $9.99 a month for 5,000 credits (email validation API pricing).

Sources

Checked October 10, 2026: WooCommerce Developer Docs, Prevent PO box addresses in the Checkout block (block vs. classic hooks); WooCommerce source, class-wc-checkout.php (woocommerce_after_checkout_validation, is_email() check) and OrderController.php (woocommerce_checkout_validate_order_before_payment, since 9.9.0, error code and status); WooCommerce, Customizing the cart and checkout (block default since 8.3, classic placeholder); WordPress Developer Reference, wp_remote_get(), add_query_arg() and set_transient(); Zapier, Emailvalidation + WooCommerce.

Email validation in other apps

Frequently asked questions

How do I validate emails in WooCommerce?

WooCommerce checks the format of the billing email. To check that the mailbox exists, hook into checkout validation and call an email validation API: woocommerce_after_checkout_validation for the classic checkout and woocommerce_checkout_validate_order_before_payment for the Checkout block. The plugin on this page does both.

Why don’t classic checkout hooks work with the Checkout block?

The Checkout block places orders through the Store API, and WooCommerce’s documentation states that classic (shortcode) checkout hooks such as woocommerce_after_checkout_validation don’t run there. Use woocommerce_checkout_validate_order_before_payment, available since WooCommerce 9.9.

Will the check slow down checkout?

It adds one API request when the customer places the order. The plugin waits at most 10 seconds, lets the order through if the API doesn’t answer, and caches each result for a day so a second attempt doesn’t call the API again.

Is this the same as asking customers to confirm their email?

No. A mailbox check tells you the address can receive mail. It doesn’t prove the customer owns it; for that you send a confirmation link (double opt-in), for example for newsletter sign-ups.

Email validation API

Validate emails in your app

emailvalidation.io checks syntax, MX records and the mailbox over SMTP, flags disposable, role and free addresses and returns a quality score, in one request.

/v1/info Email validation API Read the documentation

100 free validations every month. No credit card required.

GET https://api.emailvalidation.io/v1/info?email=support@emailvalidation.io

{
  "email": "support@emailvalidation.io",
  "user": "support",
  "tag": "",
  "domain": "emailvalidation.io",
  "format_valid": true,
  "mx_found": true,
  "smtp_check": true,
  "catch_all": null,
  "role": true,
  "disposable": false,
  "free": false,
  "score": 0.64,
  "state": "deliverable",
  "reason": "valid_mailbox",
  "did_you_mean": ""
}

Keep reading

Start using our email validation software today!

Get 100 validations per month for free