ContactLintAPIMCPPricingFree toolsCompareGet a key

Home / Integrations / WordPress

Validate Emails in WordPress Forms With ContactLint

Published 2026-10-18

This guide adds the ContactLint widget to your WordPress site, so people see "Did you mean ...?" before they submit. It also adds a server-side check in the core registration form and in Contact Form 7. The server check stops bad addresses even when someone skips the browser hint.

Where the check goes in WordPress

WordPress gives you two places to check an address. The ContactLint widget runs in the browser and shows Did you mean jane@gmail.com? under the email field. A PHP check runs on your server when the form is submitted, right before WordPress saves the user or sends the message.

Use both, but trust the server. Anyone can turn off JavaScript or post to your form directly, so the browser hint is only a convenience. The server check cannot be skipped, and the server is the only place your secret key may live.

This guide uses two well known filters. registration_errors covers the core WordPress registration form. wpcf7_validate_email* and wpcf7_validate_email cover Contact Form 7. For other form plugins, use the widget plus the contactlint:result event shown in step 1, or follow that plugin's documentation for its own validation hook.

Step 1: add the widget

  1. Get a free secret key at /signup.
  2. Create a publishable key restricted to your site's origins. A key accepts up to 5 origins, so include staging if you have it.
curl -X POST https://contactlint.com/v1/widget/keys \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "content-type: application/json" \
  -d '{"origins":["https://yoursite.com","https://staging.yoursite.com"],"daily_cap":200}'

The response contains a publishable_key that starts with cl_pub_. It is the only key that goes into page code.

Add the script in your theme's functions.php or a code snippets plugin:

add_action('wp_head', function () {
    echo '<script src="https://contactlint.com/widget.js" data-key="cl_pub_..." data-selector="input[type=email]" defer></script>';
});

If you use a header code plugin instead, paste only the script tag. data-selector lets the widget find email fields you cannot add attributes to. To limit it to one form, use a narrower selector such as #signup-form input[type=email]. More options are in the framework setup guide.

If your form plugin has no server hook you trust, you can still hold back submission in the browser. Add this after the widget script:

<script>
document.addEventListener('contactlint:result', function (e) {
  var r = e.detail;
  var bad = r.verdict === 'invalid' || (r.disposable && !r.did_you_mean);
  e.target.setCustomValidity(bad ? 'Please use a permanent, working email address.' : '');
});
document.addEventListener('input', function (e) {
  if (e.target.type === 'email') e.target.setCustomValidity('');
});
</script>

The second listener clears the block whenever the person edits the field. That way a failed check never leaves the form stuck.

Step 2: add the server-side check

Set an environment variable named CONTACTLINT_KEY to your secret key in your hosting panel or server config. Keep it out of theme files and version control. Then add this to functions.php or a code snippets plugin:

function contactlint_check_email($email) {
    $key = getenv('CONTACTLINT_KEY');
    if (!$key) {
        return array('ok' => true); // no key set: fail open
    }
    $response = wp_remote_post('https://contactlint.com/v1/email/validate', array(
        'timeout' => 3,
        'headers' => array(
            'Authorization' => 'Bearer ' . $key,
            'Content-Type'  => 'application/json',
        ),
        'body' => wp_json_encode(array('email' => $email)),
    ));
    if (is_wp_error($response) || (int) wp_remote_retrieve_response_code($response) !== 200) {
        return array('ok' => true); // timeout, quota or other error: fail open
    }
    $data = json_decode(wp_remote_retrieve_body($response), true);
    if (empty($data['result'])) {
        return array('ok' => true);
    }
    $r = $data['result'];
    if ($r['verdict'] === 'invalid') {
        return array('ok' => false, 'message' => 'That email address cannot receive mail.');
    }
    if (!empty($r['did_you_mean'])) {
        return array('ok' => false, 'message' => 'Did you mean ' . $r['did_you_mean'] . '?');
    }
    if (!empty($r['disposable'])) {
        return array('ok' => false, 'message' => 'Please use a permanent email address.');
    }
    return array('ok' => true);
}

// Core WordPress registration form
add_filter('registration_errors', function ($errors, $sanitized_user_login, $user_email) {
    $check = contactlint_check_email($user_email);
    if (!$check['ok']) {
        $errors->add('contactlint', esc_html($check['message']));
    }
    return $errors;
}, 10, 3);

// Contact Form 7 email fields, required and optional
function contactlint_cf7_validate($result, $tag) {
    $value = isset($_POST[$tag->name]) ? trim(wp_unslash($_POST[$tag->name])) : '';
    if ($value !== '') {
        $check = contactlint_check_email($value);
        if (!$check['ok']) {
            $result->invalidate($tag, $check['message']);
        }
    }
    return $result;
}
add_filter('wpcf7_validate_email*', 'contactlint_cf7_validate', 20, 2);
add_filter('wpcf7_validate_email', 'contactlint_cf7_validate', 20, 2);

The 3 second timeout and the early return array('ok' => true) lines make this fail open. If ContactLint is slow, unreachable, or returns an error such as 402 quota_exceeded, the signup goes through.

Handling each verdict and did_you_mean

Testing it

  1. Open your registration or contact form, type jane@gmial.com and click out of the field. The widget should show "Did you mean jane@gmail.com?".
  2. Submit jane@gmial.com anyway. The server check should return "Did you mean jane@gmail.com?" as a form error.
  3. Submit x@mailinator.com. You should see "Please use a permanent email address."
  4. Submit jane@example.invalid. It should be rejected as unable to receive mail.
  5. Test fail open: set CONTACTLINT_KEY to a wrong value. The API returns 401 and the signup should go through. Put the real key back.

If the widget stays silent, check that your exact origin (including www or not) is in the publishable key's origin list.

Limits

More detail: server-side validation and the widget guide.

Questions

Does this work with WooCommerce or other form plugins?

The widget works on any email input that matches your data-selector, including checkout fields. The server code here only hooks core registration and Contact Form 7. For other plugins, use the contactlint:result event in the browser, or add the helper function to that plugin's own validation hook from its documentation.

Can I put my secret key in the widget script?

No. Page code is public. Use a cl_pub_ publishable key in the script tag and keep the secret key in the CONTACTLINT_KEY environment variable on your server.

What happens if ContactLint is down?

The request times out after 3 seconds, the helper returns ok, and the signup goes through. The widget shows no hint, and the form still submits.

Other integrations

Get a free API key. 100 checks per month, no card required. Or try a live check first.