How to send email verification code for a form?

You need to put together some php & db calls to handle the form submission, generate some unique code for each email and sending the verification code.

Below I added a very basic example that can achieve that. It’s not a production ready code but tested and it works just fine.

Firstly, you need to add form tag with post method, so you can get the data in php.

<?php
    global $verification_status;
?>
<div class="container" style="padding: 10px;">
    <form action="<?php the_permalink(); ?>" method="post">
        <?php wp_nonce_field( 'verify-email' ); ?>
        <input class="email-input" name="email" type="email">
        <?php if ( -1 === $verification_status ) { ?>
            <input class="code-input" name="code" type="text" maxlength="6" required>
        <?php } ?>
        <button class="submit-btn"><?php echo -1 === $verification_status ? 'Send Code': 'Submit'; ?></button>
        <?php if ( null !== $verification_status ) { ?>
            <div class="msg-container">
                <?php if ( true === $verification_status ) { ?>
                    <p class="code-yes">Success!</p>
                <?php } elseif ( false === $verification_status ) { ?>
                    <p class="code-no">Wrong code!</p>
                <?php } else if ( 0 === $verification_status ) { ?>
                    <p class="code-sent">Code sent, please check your email.</p>
                <?php } else if ( 1 === $verification_status ) { ?>
                    <p class="code-sent">Already verified!</p>
                <?php } ?>
            </div>
        <?php } ?>
    </form>
</div>
<?php

You can add this to a page-template or shortcode.

Then to handle the form submission add a form handler with the init or wp_loaded hook.

add_action( 'init', 'wpse_416542_handle_form_submission' );

function wpse_416542_handle_form_submission() {
    global $verification_status;

    $nonce = ! empty( $_POST['_wpnonce'] ) ? sanitize_text_field( $_POST['_wpnonce'] ) : '';
    $email = ! empty( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : '';
    $code  = ! empty( $_POST['code'] ) ? sanitize_text_field( $_POST['code'] ) : '';

    if ( ! $code && $email && wp_verify_nonce( $nonce, 'verify-email' ) ) {
        $email_list = get_option( 'email_verification_list', [] );
        if ( isset( $email_list[ $email ] ) && true === $email_list[ $email ] ) {
            $verification_status = true;
            // do other staff.
            return;
        }

        $rand_code = wp_rand( 100000, 999999 );

        if ( ! empty( $email_list ) ) {
            // lets make it unique
            do {
                $rand_code = wp_rand( 100000, 999999 );
            } while ( in_array( $rand_code, array_values( $email_list ) ) );

        }

        $email_list[ $email ] = $rand_code;

        update_option( 'email_verification_list', $email_list );

        $message="Hi," . PHP_EOL . PHP_EOL;
        $message .= 'Your verification code is: ' . $rand_code;

        wp_mail( $email, 'Email Verification', $message );

        $verification_status = -1;
        return;
    }

    if ( $code && $email && wp_verify_nonce( $nonce, 'verify-email' ) ) {
        $email_list = get_option( 'email_verification_list', [] );

        if ( isset( $email_list[ $email ] ) ) {
            if ( (int) $code === (int) $email_list[ $email ] ) {
                $email_list[ $email ] = true; // Mark as verified.
                $verification_status  = true;
                update_option( 'email_verification_list', $email_list );
            } else {
                $verification_status = false;
            }
        }
    }
}

In the above form handler, if only email is submitted then

  • Check if the email is already verified.
  • If not then generate a OTP and sent it to the requested email address.

If both email and code is submitted

  • Check if email is already verified
  • If not Check the verification code submitted with stored code.

This example uses option table storing unencrypted verification codes. which is not very secure and also not very performant.

For production site you should use separate table to store the verification code. Also hash the code for security.

It is possible to use the password hasher but it recommend to use a TOTP library that can generate the OTP hash it and very the submitted code with the hash, and can automatically expire the OTP after a defined time period.