Email Validation for WooCommerce Membership Sites

workerslab ·

I run a WooCommerce membership site that sells access to video courses. Last quarter I noticed something ugly in my Stripe dashboard. Eleven percent of my subscription renewals had failed. Not because cards expired. Because the renewal reminder emails bounced, customers never saw them, and their cards got replaced without updating the billing info.

That’s recurring revenue just evaporating. Every single month.

Regular stores lose a one-time sale when an email bounces. Membership sites lose that customer’s entire lifetime value. A $29/month member who churns after three months because of a bad email address? That’s $348/year gone. Multiply that across dozens of members and you’re bleeding thousands.

Why Membership Sites Have a Bigger Email Problem

Standard WooCommerce stores send a few emails per customer. Order confirmation, shipping notification, maybe a review request. If the address is bad, you lose those touchpoints. Annoying but survivable.

Membership sites rely on email for everything.

Renewal reminders. WooCommerce Subscriptions sends automated emails before each renewal. According to Recurly’s churn benchmarks, involuntary churn from failed payments can account for up to 40% of all subscription cancellations depending on the business. A bounced renewal reminder means the member doesn’t update their expired card. The payment fails. The membership cancels. You just lost a customer who wanted to stay.

Content access links. New lesson available? Exclusive download unlocked? Members can’t access what they’re paying for if the notification lands in a dead inbox. SaaS companies that send proactive engagement emails consistently see higher retention rates. Members who don’t hear from you disengage, and disengaged members don’t renew.

Password resets and account recovery. A member who can’t reset their password can’t log in. A member who can’t log in opens a support ticket. Or worse, they just leave. According to Stytch, 75% of users who enter a password reset flow drop out before completing it. If the reset email never arrives, they have zero chance.

Trial abuse with disposable emails. This one is membership-specific. Free trials attract disposable email addresses like nothing else. Someone grabs a 7-day trial with a throwaway address, consumes the content, and signs up again with another throwaway. AtData’s 2025 research flagged that 46% of disposable domains they identified are “hyper-disposable” with lifespans under seven days. Perfectly timed for trial abuse.

How many of your “failed trials” are actually the same person on their fifth free pass?

Validating at Registration

WooCommerce Memberships ties membership to user accounts. That means the registration form is your first line of defense.

WordPress registration uses the registration_errors filter. Hook into it and validate before the account ever gets created.

add_filter('registration_errors', 'validate_membership_registration_email', 10, 3);

function validate_membership_registration_email($errors, $sanitized_user_login, $user_email) {
    if (empty($user_email)) {
        return $errors;
    }

    $domain = strtolower(substr(strrchr($user_email, '@'), 1));

    // Block disposable email domains
    $disposable_domains = [
        'mailinator.com',
        'guerrillamail.com',
        'tempmail.com',
        'throwaway.email',
        'yopmail.com',
        'sharklasers.com',
        'dispostable.com',
        'trashmail.com',
    ];

    if (in_array($domain, $disposable_domains, true)) {
        $errors->add(
            'disposable_email',
            'Disposable email addresses aren\'t accepted. Please use your regular email to create a membership account.'
        );
        return $errors;
    }

    // Check MX records
    if (!checkdnsrr($domain, 'MX')) {
        $errors->add(
            'invalid_domain',
            'This email domain doesn\'t appear to accept emails. Please double-check your address.'
        );
    }

    return $errors;
}

This catches two problems at once. Disposable addresses get blocked. Domains without mail servers get flagged. The customer sees a clear error and can fix it before the account is created.

Static domain lists only go so far though. The disposable email blocklist post covers why static lists miss the long tail of rotating throwaway domains.

Validating at Membership Checkout

Registration catches free trial abuse. Checkout catches bad emails on paid memberships.

WooCommerce Memberships creates memberships through regular WooCommerce orders. So the woocommerce_checkout_process hook works here. But membership products need stricter validation than a one-time purchase.

add_action('woocommerce_checkout_process', 'validate_membership_checkout_email');

function validate_membership_checkout_email() {
    $email = sanitize_email($_POST['billing_email'] ?? '');

    if (empty($email)) {
        return;
    }

    // Only apply strict validation for membership/subscription products
    $cart_has_membership = false;
    foreach (WC()->cart->get_cart() as $cart_item) {
        $product = $cart_item['data'];
        $product_id = $product->get_id();

        // Check if this product grants access to any membership plan
        $grants_membership = false;
        if (function_exists('wc_memberships_get_membership_plans')) {
            foreach (wc_memberships_get_membership_plans() as $plan) {
                if ($plan->has_product($product_id)) {
                    $grants_membership = true;
                    break;
                }
            }
        }

        if (
            $grants_membership ||
            (class_exists('WC_Subscriptions_Product') && WC_Subscriptions_Product::is_subscription($product))
        ) {
            $cart_has_membership = true;
            break;
        }
    }

    if (!$cart_has_membership) {
        return;
    }

    // Run API validation for membership products
    $api_key = get_option('truemail_api_key');
    $response = wp_remote_post(
        'https://api.truemail.io/v1/verify',
        [
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ],
            'body'    => wp_json_encode(['email' => $email]),
            'timeout' => 5,
        ]
    );

    if (is_wp_error($response)) {
        return; // Let the order through on API timeout
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);
    $status = $body['status'] ?? 'unknown';

    if ($status === 'invalid') {
        wc_add_notice(
            'This email address can\'t receive emails. Since membership access and renewal notices go to this address, please use a working email.',
            'error'
        );
    }

    if ($status === 'disposable') {
        wc_add_notice(
            'Disposable emails aren\'t accepted for memberships. You\'ll need a permanent address for renewal notices and account access.',
            'error'
        );
    }
}

Notice the cart check at the top. It loops through membership plans to see if the product grants access to any of them, and checks for subscription product types separately. One-time purchases still go through with basic validation. Only membership and subscription products trigger the strict API check. Your regular customers won’t see any friction.

Why the extra strictness for memberships? Because a bad email on a membership isn’t a single failed delivery. It’s months of failed renewal reminders, missed content notifications, and eventually involuntary churn. The WooCommerce email validation checkout guide covers general checkout validation. This takes it further for recurring products.

Handling Subscription Renewal Emails

Even if you validate at signup, emails go bad over time. People change jobs, switch providers, abandon old inboxes. Email lists decay at roughly 22-28% per year. A member who signed up with a valid work email in January might be unreachable by October.

WooCommerce Subscriptions fires a woocommerce_scheduled_subscription_payment action before each renewal charge. You can hook into the renewal process and check whether the member’s email is still deliverable.

add_action('woocommerce_scheduled_subscription_payment', 'check_subscriber_email_before_renewal', 5);

function check_subscriber_email_before_renewal($subscription_id) {
    $subscription = wcs_get_subscription($subscription_id);

    if (!$subscription) {
        return;
    }

    $email = $subscription->get_billing_email();
    $api_key = get_option('truemail_api_key');

    $response = wp_remote_post(
        'https://api.truemail.io/v1/verify',
        [
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ],
            'body'    => wp_json_encode(['email' => $email]),
            'timeout' => 5,
        ]
    );

    if (is_wp_error($response)) {
        return;
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    if (($body['status'] ?? '') === 'invalid') {
        $subscription->add_order_note(
            'Billing email failed validation. Renewal reminder may not deliver. Consider reaching out via alternative contact method.'
        );
        // Tag for admin follow-up (HPOS-compatible)
        $subscription->update_meta_data('_email_validation_failed', 'yes');
        $subscription->save();
    }
}

This doesn’t block the renewal. It flags the problem so you can reach out to the member through other channels before the payment fails. The update_meta_data and save calls are HPOS-compatible, so this works whether your store uses the legacy post-based storage or WooCommerce’s newer custom order tables. A quick admin dashboard filter on _email_validation_failed gives you a list of at-risk subscribers to contact.

Proactive beats reactive. Every time.

Periodic Member List Cleaning

Checking emails one at a time during renewals works. But if you’ve got hundreds or thousands of members, a quarterly bulk validation catches problems before they cause churn.

// WP-CLI command for bulk member email validation
if (defined('WP_CLI') && WP_CLI) {
    WP_CLI::add_command('membership validate-emails', function () {
        $memberships = wc_memberships_get_user_memberships(null, ['status' => ['active']]);
        $api_key = get_option('truemail_api_key');
        $flagged = 0;

        foreach ($memberships as $membership) {
            $user = get_user_by('id', $membership->get_user_id());
            if (!$user) continue;

            $response = wp_remote_post(
                'https://api.truemail.io/v1/verify',
                [
                    'headers' => [
                        'Authorization' => 'Bearer ' . $api_key,
                        'Content-Type'  => 'application/json',
                    ],
                    'body'    => wp_json_encode(['email' => $user->user_email]),
                    'timeout' => 10,
                ]
            );

            if (is_wp_error($response)) continue;

            $body = json_decode(wp_remote_retrieve_body($response), true);

            if (in_array($body['status'] ?? '', ['invalid', 'disposable'])) {
                update_user_meta($user->ID, '_email_validation_status', $body['status']);
                $flagged++;
                WP_CLI::log("Flagged: {$user->user_email} ({$body['status']})");
            }

            usleep(200000); // Rate limit: 5 requests/second
        }

        WP_CLI::success("Done. Flagged {$flagged} members with invalid emails.");
    });
}

Run wp membership validate-emails once per quarter. Export the flagged members and reach out via phone, SMS, or on-site notification asking them to update their email address.

The email validation for e-commerce guide covers the full lifecycle of email hygiene across platforms. For membership sites, this quarterly sweep is the minimum.

Stopping Free Trial Abuse

Free trials are the biggest disposable email magnet. Someone signs up with a burner address, gets 7 or 14 days of access, and never converts. Then they do it again. And again.

The registration validation hook above catches known disposable domains. But dedicated abusers use fresh domains that rotate weekly. You need behavioral signals too.

add_filter('registration_errors', 'detect_trial_abuse_signals', 20, 3);

function detect_trial_abuse_signals($errors, $sanitized_user_login, $user_email) {
    $domain = strtolower(substr(strrchr($user_email, '@'), 1));

    // Check domain age via MX records as a proxy
    // New domains with no established MX are suspicious for trial signups
    $mx_records = [];
    getmxrr($domain, $mx_records);

    if (empty($mx_records)) {
        $errors->add(
            'suspicious_domain',
            'We couldn\'t verify your email domain. Please use an email address from an established provider.'
        );
        return $errors;
    }

    // Check if this IP has created multiple trial accounts
    $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    $recent_signups = get_transient('trial_signups_' . md5($ip));

    if ($recent_signups && $recent_signups >= 2) {
        $errors->add(
            'too_many_trials',
            'Multiple trial accounts from this connection have been detected. Please contact support for assistance.'
        );
    }

    return $errors;
}

Layer this with API-based disposable detection for the strongest protection. The static domain list catches the obvious throwaway services. The MX check catches domains that don’t have real mail infrastructure. The IP throttle catches repeat abusers regardless of which email they use.

Is it bulletproof? No. Determined abusers use VPNs. But you don’t need to stop every single one. You need to make it annoying enough that most people just pay for a subscription instead.

What to Expect After Setup

Membership site owners who add email validation across registration, checkout, and renewals typically see changes within 60 days.

Involuntary churn drops. If 40% of your cancellations come from failed payments tied to unreachable emails, even fixing half of those is significant. One site I talked to recovered $2,100/month in renewals that would have lapsed.

Trial abuse decreases. Blocking disposable emails at registration eliminates the bulk of repeat trial signups. Not every trial abuser disappears, but the casual ones who rely on throwaway email services do. The determined ones who register fresh domains are a smaller problem.

Support tickets drop. Fewer “I can’t reset my password” and “I didn’t get my renewal notice” tickets. Members who can actually receive email don’t need hand-holding.

Engagement goes up. Content notifications that actually arrive produce members who actually use the membership. Active members renew at higher rates. Disengaged members don’t. And engagement compounds. Members who regularly see your content updates build a habit around your product.

The Membership Email Checklist

Here’s the setup in order:

  1. Add disposable email blocking at registration using the registration_errors filter.
  2. Add API validation at checkout for membership and subscription products.
  3. Hook into subscription renewals to flag members with failing email addresses.
  4. Run quarterly bulk validation on your active member list.
  5. Set up IP-based throttling to limit trial abuse from repeat offenders.
  6. Monitor involuntary churn rate and support ticket volume monthly.

Each layer catches a different problem. Registration blocks throwaway signups. Checkout blocks bad emails on paid memberships. Renewal checks catch emails that went bad after signup. Bulk cleaning catches everything else.

Your members signed up because they want what you’re offering. Don’t let a bad email address be the reason they leave.