Email Verify: hooks for developers

You do not need any of this to use the plugin. WPCoreTools Email Verify works complete out of the box: install it, run the wizard, done. This page is for developers who want to extend it from a small plugin of their own, without editing the plugin's files. Every filter and action is listed, with what it is for and an example you can copy.

Before you start

Nothing on this page is required. The plugin is complete without a single line of code: install it, run the setup wizard, and it works. Every hook below is an optional extension point for developers. The settings page and the wizard cover everything a site owner normally wants: what an unconfirmed customer has to wait for, how long a link stays valid, the email text, the resend wait, exempt roles, and since 1.0.8 the page a customer sees after confirming. Look there first. Hooks are for the cases a setting cannot cover: a decision that depends on the user, a text that depends on the page, or telling another system what happened.

Where to put your code

In a small plugin of your own in wp-content/plugins/, or in a file in wp-content/mu-plugins/. Not in the theme's functions.php: a theme switch would silently drop it. A plugin file needs only a header:

<?php
/**
 * Plugin Name: My site: Email Verify tweaks
 */

// Your add_filter() and add_action() calls go here.

How to read this page

  • Filters let you change a value the plugin is about to use. Always return a value; return what you were given to change nothing.
  • Actions tell you that something happened. They cannot change the outcome.
  • Hooks marked since 1.0.8 need version 1.0.8 or later. Everything else exists since the first public release.
  • The one-line signature under each heading shows the parameter types for reading; it is not code to paste. The Example block is.
  • All hook names start with wpcev_. Every parameter called $user_id is a WordPress user ID; get_userdata( $user_id ) gives you the rest.

Three things to leave alone. Never log or store the plaintext $token some filters receive. Do not use a filter to wave a customer through a gate without a reason you can explain (the plugin's security depends on those gates). And when a hook hands you a $user_id of 0, it means the request proved nothing about who the user is, so do not personalise or write anything on it.

Who counts as confirmed

The plugin holds a user back when their account carries the flag "not confirmed". Users who registered before the plugin was activated and users in an exempt role are never held back. These hooks change that decision or ask for it.

wpcev_is_blocked filter

The final word on whether a user is held back. Every gate (login, ordering, comments, wp-admin) and every notice asks this.

apply_filters( 'wpcev_is_blocked', bool $blocked, int $user_id )

Parameters

$blockedtrue when the plugin would hold this user back (the account carries the "not confirmed" flag), false when it would let them through (confirmed, or registered before the plugin was activated). The filter is not called for users in an exempt role or for user ID 0: the plugin never holds those back, whatever a callback returns.
$user_idThe user being checked.

Use it when

Another system already knows the address is good, for example members imported from an old site or accounts created through single sign-on, and you want them never to be held back without marking each one by hand.

Example

add_filter( 'wpcev_is_blocked', function ( $blocked, $user_id ) {
    // Members imported from the old shop proved their address there.
    if ( get_user_meta( $user_id, 'imported_from_legacy_shop', true ) ) {
        return false;
    }
    return $blocked;
}, 10, 2 );

Good to know

  • It runs on every check, several times per page. Keep the callback cheap: read a user meta or a role, do not query an API.
  • Returning true for a user the plugin would let through, for example a confirmed user or one who registered before activation, does hold them back. Be sure that is what you want. Users in an exempt role are decided before the filter runs, so a callback cannot hold them back; remove the role from the exempt roles in the settings instead.
  • It changes the answer, not the stored status: the Users list still shows the user as pending. To change the status, see calling the plugin from your code.

wpcev_is_verified filter

Ask the plugin from your own code whether a user is confirmed.

$confirmed = apply_filters( 'wpcev_is_verified', false, int $user_id );

Parameters

falsePass false. The plugin replaces the value with its answer; if the plugin is not active, your code gets false back and treats the user as not confirmed.
$user_idThe user to ask about.

Use it when

Your own code (a form plugin, a members-only download, a custom checkout) should refuse unconfirmed users too, and you want the same answer the plugin gives, including exempt roles, pre-existing users and anything a wpcev_is_blocked callback decided.

Example

add_filter( 'my_downloads_allowed', function ( $allowed, $user_id ) {
    if ( ! $user_id || ! apply_filters( 'wpcev_is_verified', false, $user_id ) ) {
        return false; // not logged in, or not confirmed yet
    }
    return $allowed;
}, 10, 2 );

Good to know

  • This is a query, not a setting: adding your own callback to it would change the answer the plugin gives back to everyone else. Use wpcev_is_blocked for that.
  • "Confirmed" here means "not held back": users who registered before activation and exempt roles also answer true.
  • The answer is about accounts only. For user ID 0 (a visitor who is not logged in) it is true, because the plugin holds back no one who is not an account. If guests must be refused too, check $user_id > 0 yourself first, as the example does.

wpcev_skip_verification_on_register filter

Do not ask a new account to confirm. It is marked confirmed at once and no email is sent.

apply_filters( 'wpcev_skip_verification_on_register', bool $skip, int $user_id )

Parameters

$skipDefault false. Return true to skip.
$user_idThe account that was just created.

Use it when

The account was created in a way that already proved the address: a single sign-on plugin, an import, or an administrator creating the account in wp-admin.

Example

add_filter( 'wpcev_skip_verification_on_register', function ( $skip, $user_id ) {
    // Accounts an administrator creates in wp-admin need no confirmation.
    if ( is_admin() && current_user_can( 'create_users' ) ) {
        return true;
    }
    return $skip;
}, 10, 2 );

Good to know

  • The user is marked confirmed with the reason filter. Because nothing proved the inbox, this is not passed on to WooCommerce 11's own confirmation record and does not link earlier guest orders.
  • It runs once, during registration. It has no effect on existing accounts.

wpcev_verify_on_password_reset filter

Whether finishing a password reset confirms an unconfirmed account. Default: yes, because the reset email reached the inbox.

apply_filters( 'wpcev_verify_on_password_reset', bool $verify, int $user_id )

Parameters

$verifyDefault true.
$user_idThe account whose password was reset.

Example

// A password reset should not count as email confirmation on this site.
add_filter( 'wpcev_verify_on_password_reset', '__return_false' );

Good to know

  • Covers both WordPress's own reset and WooCommerce's lost-password form.
  • Users who registered before activation are never touched by this.

The verification email

The subject, body and sender are settings. These hooks cover what the settings cannot: a lifetime per user, a different link, extra headers, and knowing when a mail went out or failed.

wpcev_expiry_days filter since 1.0.8

How many days a verification link stays valid for one user. The setting is the default for everyone.

apply_filters( 'wpcev_expiry_days', int $days, int $user_id )

Parameters

$daysThe setting (7 by default).
$user_idThe user the link is for.

Use it when

One group needs a different window: a live cohort that must confirm within a day, or invited business customers who get a month.

Example

add_filter( 'wpcev_expiry_days', function ( $days, $user_id ) {
    $user = get_userdata( $user_id );
    if ( $user && in_array( 'wholesale_customer', (array) $user->roles, true ) ) {
        return 30;
    }
    return $days;
}, 10, 2 );

Good to know

  • The result is clamped to 1 to 90 days, the same range as the setting. A link can never be made permanent.
  • The filter runs when the email is written (the %expiry% and %expiry_date% placeholders) and again when the link is clicked. Decide from something that does not change in between, such as the role at registration or a meta you set yourself, or the email and the check disagree.
  • The WooCommerce email preview in wp-admin has no real user and shows the setting.

wpcev_verification_url filter

The link placed in the email.

apply_filters( 'wpcev_verification_url', string $url, int $user_id, string $token )

Parameters

$urlThe home URL with wpcev_verify=<token> and uid=<id>.
$user_idThe recipient.
$tokenThe plaintext token. Never log or store it.

Example

add_filter( 'wpcev_verification_url', function ( $url, $user_id, $token ) {
    // Tag clicks from the verification mail in analytics.
    return add_query_arg( 'utm_source', 'verification-email', $url );
}, 10, 3 );

Good to know

  • Keep the wpcev_verify and uid parameters and keep the link on this site. The plugin handles it on any page of the site, so a different path is fine; a different host is not.
  • The visitor is shown a "Confirm my email" button first unless they are logged in as that account. That protects against mail scanners and is not something a filter should work around.

wpcev_verification_email filter

Subject, body, headers and recipient of the plain-text email, after the placeholders were filled in.

apply_filters( 'wpcev_verification_email', array $args, string $token )

Parameters

$argsAn array with subject, body, headers (array of strings), to and user_id.
$tokenThe plaintext token, in case you build your own link. Never log or store it.

Example

add_filter( 'wpcev_verification_email', function ( $args, $token ) {
    $args['headers'][] = 'Reply-To: Student services <help@example.com>';
    $args['body']     .= "\n\nQuestions? Reply to this email and we will help.";
    return $args;
}, 10, 2 );

Good to know

  • Only for the plain-text format. When the email format setting is WooCommerce's template, the mail is a WooCommerce email with the id wpcev_verification: edit it under WooCommerce > Settings > Emails, or use WooCommerce's own filters such as woocommerce_email_subject_wpcev_verification.
  • For the text alone the settings page is enough: subject and body are editable there, with placeholders.

wpcev_verification_sent action since 1.0.8

A verification email was handed to wp_mail() and accepted.

do_action( 'wpcev_verification_sent', int $user_id )

Parameters

$user_idThe recipient. The address is get_userdata( $user_id )->user_email.

Use it when

You keep an activity log, a CRM timeline or a funnel (registered, mail sent, confirmed) and want the "sent" step without wrapping wp_mail.

Example

add_action( 'wpcev_verification_sent', function ( $user_id ) {
    my_crm()->timeline( $user_id, 'Verification email sent' );
} );

Good to know

  • Fires on every path: the first mail at registration, a resend from a notice or the login page, a changed address, an admin's Resend button and WP-CLI.
  • "Accepted by wp_mail()" is not "delivered". Delivery is the mail server's business.
  • The token and the link are deliberately not passed.

wpcev_verification_send_failed action since 1.0.8

A verification email could not be sent: wp_mail() returned false.

do_action( 'wpcev_verification_send_failed', int $user_id, string $code )

Parameters

$user_idThe customer who did not get the mail.
$codewp_mail_failed.

Use it when

You want to know the moment the site's mail stops working, in a channel that does not depend on that mail.

Example

add_action( 'wpcev_verification_send_failed', function ( $user_id, $code ) {
    wp_remote_post( 'https://alerts.example.com/hooks/mail', array(
        'timeout' => 3,
        'body'    => array( 'site' => home_url(), 'user' => $user_id, 'code' => $code ),
    ) );
}, 10, 2 );

Good to know

  • The plugin also writes the failure to the "Failed sends" tab and Site Health reports recent failures. This action is for your own alerting.
  • The customer's previous link, if there was one, stays valid: a failed resend never destroys a working link.

What customers see

Wording and placement of what an unconfirmed customer reads, and where a confirmed one lands. For a different language, translate the plugin instead; these hooks are for a different message, or no message, on your site.

wpcev_notice_message filter since 1.0.8

The text of the main notices an unconfirmed customer reads, with a context that says where it is shown.

apply_filters( 'wpcev_notice_message', string $html, string $context, int $user_id )

Parameters

$htmlThe message, already escaped. In account, order and registration it contains <strong> (the address). account and order also carry the resend link (<a class="wpcev-resend"> with its countdown attribute), and order the "Change it" link. login and the two banners are plain sentences.
$contextaccount (the My Account notice), order (cart, classic checkout, the Cart and Checkout blocks, and the Pay for order page, whose text has no links), registration (the one-time notice after signing up), login (the login error), frontend_banner (the banner on sites without WooCommerce) or admin_banner (the wp-admin banner).
$user_idThe customer. 0 for login, where the user is not known yet.

Use it when

The wording does not fit your site: a school talks to students, not customers; a members' club wants a different tone; you want to add a sentence about the spam folder.

Example

add_filter( 'wpcev_notice_message', function ( $html, $context, $user_id ) {
    if ( 'login' === $context ) {
        return 'Your student account is waiting for email confirmation. Open the link we sent you, then log in.';
    }
    if ( 'account' === $context ) {
        return $html . ' <strong>Tip:</strong> check your spam folder too.';
    }
    return $html;
}, 10, 3 );

Good to know

  • Only <a href class data-wpcev-available-at> and <strong> survive; everything else is stripped. An empty return keeps the original.
  • If you replace a message that contained the resend link, the customer loses the way to ask for a new email from that place. Keep the link: it is the <a class="wpcev-resend" ...> in $html.
  • For login the value must be identical on every call (no user, no time, no randomness): the plugin calls the filter again to find its own login error by this text and add the resend link on WooCommerce login forms. Keep registration stable for one user too; the plugin recognises that notice in the queue the same way.
  • Short one-off messages are not passed through this filter: the result after a link is opened, the resend results, the comment refusal and the disabled Place order note. Reword those with a translation file (for example with Loco Translate), text domain wpcoretools-email-verify.
  • For frontend_banner and admin_banner you get the sentence only; the plugin adds the resend link after it.

wpcev_show_notice filter since 1.0.8

Hide the notice for an unconfirmed customer on some pages. Display only: the gates still apply.

apply_filters( 'wpcev_show_notice', bool $show, string $location, int $user_id )

Parameters

$showDefault true.
$locationaccount (My Account), cart and checkout (the classic cart and checkout notice), frontend (the banner on every front-end page of a site without WooCommerce) or admin (the wp-admin banner).
$user_idThe customer.

Use it when

On a site without WooCommerce the banner is printed on every front-end page, including a course player or a landing page where it is in the way.

Example

add_filter( 'wpcev_show_notice', function ( $show, $location, $user_id ) {
    // Only on the student dashboard, nowhere else on the front end.
    if ( 'frontend' === $location && ! is_page( 'dashboard' ) ) {
        return false;
    }
    return $show;
}, 10, 3 );

Good to know

  • Hiding a notice also hides its resend link. The refusal messages on checkout and in the Store API keep theirs, and the login page offers the resend form while the setting "Allow logged-out resend" is on (it is by default).
  • The My Account notice is skipped once on the page a customer lands on right after registering, whatever the filter says: the sign-up notice already told them.
  • It does not touch the gates. A customer who sees no notice is still refused when they try to order.

wpcev_verify_redirect_url filter since 1.0.8

Where a visitor lands after the verification link worked. The setting "After confirming, send the customer to" covers the common case; this filter decides per request.

apply_filters( 'wpcev_verify_redirect_url', string $url, int $user_id, string $result, string $viewer )

Parameters

$urlThe page from the setting, or '' for the built-in result (My Account with a notice when WooCommerce is active, otherwise the plugin's own confirmation page). Return '' to keep that.
$user_idThe account that was confirmed, when the request proves it: a link used for the first time, or the owner is logged in. 0 for a repeated click from a browser that is not logged in as that account.
$resultok (confirmed just now) or already (a second click, or a mail scanner opening a link that already worked).
$viewerguest (not logged in, the usual case), self (logged in as that account) or other (logged in as a different account).

Use it when

A customer who is already logged in should go straight to the content, while a logged-out one needs a page with a login link. Or different groups have different landing pages.

Example

add_filter( 'wpcev_verify_redirect_url', function ( $url, $user_id, $result, $viewer ) {
    if ( 'self' === $viewer ) {
        return home_url( '/my-courses/' ); // logged in: straight to the content
    }
    return home_url( '/email-verification-success/' ); // a page with a login link
}, 10, 4 );

Good to know

  • The URL must be on this site; relative paths and same-host URLs are accepted. An address on another host is ignored and the built-in result is shown, rather than WordPress's usual fallback to wp-admin (the allowed_redirect_hosts filter widens the check).
  • Clicking the link does not log anyone in. A page for guest visitors should offer a login link.
  • Expired and invalid links never come here: they keep the built-in pages, which carry the resend link.
  • Runs after the account was marked confirmed and after earlier guest orders were linked, so your callback may read the new status.

wpcev_verify_landing filter since 1.0.8

Title and body of the plugin's own result page, on sites without WooCommerce. With WooCommerce the result is a My Account notice instead.

apply_filters( 'wpcev_verify_landing', array $landing, string $result )

Parameters

$landingarray( 'title' => string, 'body' => string ). The title is plain text; the body is HTML and goes through wp_kses_post().
$resultok, already, expired, expired_resent (the link had expired and a fresh one was just mailed) or invalid.

Use it when

You use the built-in page (no success URL set) but want your own words, or your own links on the expired and invalid pages, which the success URL deliberately leaves alone.

Example

add_filter( 'wpcev_verify_landing', function ( $landing, $result ) {
    if ( 'ok' === $result ) {
        $landing['title'] = 'Welcome aboard';
        $landing['body']  = '<p>Your email is confirmed.</p>'
            . '<p><a class="button" href="' . esc_url( home_url( '/login/' ) ) . '">Log in</a></p>';
    }
    return $landing;
}, 10, 2 );

Good to know

  • The built-in "Go to login" buttons use wp_login_url(). A custom login page set through WordPress's login_url filter is already honoured without this hook.
  • The HTTP status stays 200 for a confirmed address and 410 otherwise; a missing or non-string title or body keeps the default.

Events to listen for

Actions that tell your code what happened. Pick by what you need to know: that the inbox was proven, that the status changed for any reason, or that a link failed.

wpcev_email_confirmed action

The customer proved they control the inbox.

do_action( 'wpcev_email_confirmed', int $user_id, string $source )

Parameters

$user_idThe confirmed account.
$sourcelink (the verification link), password_reset (a completed reset), woocommerce (WooCommerce 11's own confirmation) or multisite (WordPress's multisite activation key).

Use it when

Something should happen only once the address is really proven: subscribe the customer to the newsletter they ticked, hand them a welcome coupon, start an onboarding sequence. The plugin itself uses this moment to link earlier guest orders to the account.

Example

add_action( 'wpcev_email_confirmed', function ( $user_id, $source ) {
    if ( get_user_meta( $user_id, 'newsletter_opt_in', true ) ) {
        my_newsletter_subscribe( get_userdata( $user_id )->user_email );
    }
}, 10, 2 );

Good to know

  • It does not fire when an admin marks a user verified, for exempt roles or for wpcev_skip_verification_on_register: nothing was proven there. For every status change, use wpcev_user_verified.
  • To redirect the visitor after the link, use wpcev_verify_redirect_url rather than redirecting from here: this action also fires during a password reset.

wpcev_user_verified action

The account was marked confirmed, for any reason.

do_action( 'wpcev_user_verified', int $user_id, string $reason )

Parameters

$user_idThe account.
$reasonlink, password_reset, multisite, woocommerce (proof of the inbox; woocommerce also covers customers the Tools-tab sync takes over from WooCommerce's own record), admin (a Mark verified action, the profile, WP-CLI), exempt (an exempt role at registration), filter (wpcev_skip_verification_on_register), or '' from code that gave no reason.

Example

add_action( 'wpcev_user_verified', function ( $user_id, $reason ) {
    update_user_meta( $user_id, 'crm_email_status', 'verified:' . $reason );
}, 10, 2 );

wpcev_user_unverified action

The account was marked not confirmed.

do_action( 'wpcev_user_unverified', int $user_id, string $reason )

Parameters

$user_idThe account.
$reasonadmin (a Mark unverified action, the profile, WP-CLI), email_change (the address was changed and must be confirmed again), register (a new account), grandfathered (the Tools-tab action for users from before the plugin), or '' from code that gave no reason.

Example

add_action( 'wpcev_user_unverified', function ( $user_id, $reason ) {
    if ( 'email_change' === $reason ) {
        my_crm()->timeline( $user_id, 'Changed email address, confirmation pending' );
    }
}, 10, 2 );

Changing the address

An unconfirmed customer can correct a mistyped address from the notice on My Account ("Wrong address? Change it"). These hooks apply to that form.

wpcev_new_email_errors filter

Refuse a new address given in the "Wrong address?" form.

apply_filters( 'wpcev_new_email_errors', WP_Error $errors, string $email, int $user_id )

Parameters

$errorsErrors found so far. Add one to refuse; the message is shown to the customer.
$emailThe new address, already checked for format, duplicates and (if installed) Disposable Email Guard.
$user_idThe account.

Example

add_filter( 'wpcev_new_email_errors', function ( $errors, $email, $user_id ) {
    if ( ! preg_match( '/@(students\.example\.edu|example\.edu)$/i', $email ) ) {
        $errors->add( 'school_domain', 'Please use your school email address.' );
    }
    return $errors;
}, 10, 3 );

Good to know

  • This covers only the plugin's own form. Registration and the Account details page are validated by WordPress and WooCommerce; use their hooks for those.

wpcev_run_wc_account_email_checks filter

Whether the "Wrong address?" form also runs other plugins' WooCommerce account-details validation on the new address.

apply_filters( 'wpcev_run_wc_account_email_checks', bool $run )

Example

// Another plugin's account validation misfires on this form: skip it here.
add_filter( 'wpcev_run_wc_account_email_checks', '__return_false' );

Good to know

  • Default true: the form fires WooCommerce's woocommerce_save_account_details_errors with the new address so plugins that restrict domains or check for duplicates keep working. Only relevant with WooCommerce.

WooCommerce

WooCommerce 11 keeps its own "Email confirmation" record per customer. The plugin keeps the two in step; one filter controls that, and another names custom reasons on the profile.

wpcev_sync_to_woocommerce filter

Whether a status change is passed on to WooCommerce 11's own confirmation record.

apply_filters( 'wpcev_sync_to_woocommerce', bool $sync, int $user_id, string $reason, string $direction )

Parameters

$syncDefault true.
$user_idThe customer.
$reasonlink, password_reset, multisite, admin or backfill. Only reasons that prove the inbox or are an admin decision are ever synced.
$directionverify or unverify.

Example

// Keep WooCommerce's record as it is when an admin unticks a customer here.
add_filter( 'wpcev_sync_to_woocommerce', function ( $sync, $user_id, $reason, $direction ) {
    if ( 'unverify' === $direction && 'admin' === $reason ) {
        return false;
    }
    return $sync;
}, 10, 4 );

Good to know

  • Does nothing on WooCommerce before 11 or without WooCommerce.
  • When WooCommerce confirms a customer through its own link, the plugin follows; that direction has no filter, WooCommerce is the authority on its own record.

wpcev_verified_via_label filter

The "how was this address verified" label on the user profile, for reasons the plugin does not know.

apply_filters( 'wpcev_verified_via_label', string $label, string $via, int $user_id )

Parameters

$labelThe label the plugin would show.
$viaThe stored reason, for example link, admin, or the reason your own code passed to mark_verified().
$user_idThe user whose profile is shown.

Example

add_filter( 'wpcev_verified_via_label', function ( $label, $via, $user_id ) {
    return 'campus_sso' === $via ? 'Campus single sign-on' : $label;
}, 10, 3 );

Auto-delete of abandoned sign-ups

Off by default. When it is on, a daily job deletes accounts that never confirmed after the chosen number of days, skipping exempt roles, anyone with WooCommerce orders and anyone with content. Three hooks let you keep an account, redirect its content, or record the deletion.

wpcev_should_purge_user filter

Keep an account the auto-delete would remove.

apply_filters( 'wpcev_should_purge_user', bool $delete, int $user_id )

Example

add_filter( 'wpcev_should_purge_user', function ( $delete, $user_id ) {
    // Students with an enrolment are kept, confirmed or not.
    if ( get_user_meta( $user_id, 'enrolment_id', true ) ) {
        return false;
    }
    return $delete;
}, 10, 2 );

wpcev_purge_reassign_to filter

The user that receives any remaining content of a deleted account. Default null: WordPress's own behaviour.

apply_filters( 'wpcev_purge_reassign_to', int|null $reassign, int $user_id )

Example

add_filter( 'wpcev_purge_reassign_to', function ( $reassign, $user_id ) {
    return 1; // the site owner's account
}, 10, 2 );

Good to know

  • Accounts with any posts, pages or media are skipped by the auto-delete anyway, so this rarely matters. It exists so that nothing is ever lost if it does.

wpcev_before_purge_user action since 1.0.8

The auto-delete is about to remove an account. The user still exists at this point.

do_action( 'wpcev_before_purge_user', int $user_id, int|null $reassign )

Parameters

$user_idThe account about to be deleted. get_userdata() still works here.
$reassignWho receives remaining content, or null.

Use it when

You keep a deletion record for data-protection purposes, or another system (a newsletter tool, a CRM) must forget the person too.

Example

add_action( 'wpcev_before_purge_user', function ( $user_id, $reassign ) {
    $user = get_userdata( $user_id );
    if ( $user ) {
        my_newsletter_unsubscribe( $user->user_email );
        my_deletion_log( 'unconfirmed account removed', $user->user_email );
    }
}, 10, 2 );

Good to know

  • This is a notification. To keep the account, use wpcev_should_purge_user, which runs first.
  • Only the plugin's auto-delete fires it. An admin deleting a user from the Users list goes through WordPress's own delete_user action.

Calling the plugin from your code

Hooks cover almost everything, but sometimes your code needs to act: confirm an account because your own system verified the address, or send a fresh link. The plugin's objects are reachable through wpcev(). Check that the function exists first, so your code survives the plugin being deactivated.

if ( function_exists( 'wpcev' ) ) {
    // Mark a user confirmed, naming your reason (shown on the profile; see wpcev_verified_via_label).
    wpcev()->tokens->mark_verified( $user_id, 'campus_sso' );

    // Ask for the stored status: 'verified', 'unverified', 'grandfathered' or 'exempt' ('invalid' for ID 0).
    $status = wpcev()->tokens->status( $user_id );

    // Send a fresh verification email, honouring the resend wait.
    // Returns 'sent', 'throttled', 'locked', 'failed' or 'invalid'.
    $result = wpcev()->mailer->send_verification( $user_id, true );
}
  • mark_verified() fires wpcev_user_verified with your reason. A reason of your own is not passed on to WooCommerce 11's record (only proofs of the inbox and admin decisions are), and does not link guest orders; call wc_update_new_customer_past_orders() yourself if that is what you want.
  • To take confirmation away, wpcev()->tokens->unmark_verified( $user_id, 'my_reason' ). It does not send a new link; call send_verification() for that.
  • From the command line the same is available as wp wpcev status|verify|unverify|resend <user>.

Missing a hook? Tell us what you are trying to do on the WordPress.org support forum (opens in a new tab) or through the contact page. Hooks are added when there is a real site that needs one; most of the ones above started as a support question.

Back to the plugin

Everything on this page is optional. The plugin page explains what Email Verify does for a site owner, no code involved.

WPCoreTools Email Verify