# Newsletter Sign-Up With Double Opt-In

> Adds a [wpide_newsletter] sign-up shortcode that stores subscribers in the admin, emails a confirmation link (double opt-in), and lets you export confirmed addresses as CSV.

- Type: PHP
- Runs: Run everywhere
- Category: [Forms](https://wpide.com/library/category/forms/)
- Tags: newsletter, shortcode, subscribers
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/form-newsletter-signup/

## Code

```php
$wpide_nl_subject = 'Please confirm your subscription';

add_action( 'init', function () {
    register_post_type( 'wpide_subscriber', array( 'public' => false, 'show_ui' => false, 'supports' => array( 'title' ) ) );
    if ( ! wp_next_scheduled( 'wpide_nl_cleanup' ) ) {
        wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpide_nl_cleanup' );
    }
} );

add_action( 'template_redirect', function () use ( $wpide_nl_subject ) {
    // Confirmation link.
    if ( isset( $_GET['wpide_nl_confirm'], $_GET['id'] ) ) {
        $id    = absint( $_GET['id'] );
        $token = sanitize_text_field( wp_unslash( $_GET['wpide_nl_confirm'] ) );
        $saved = (string) get_post_meta( $id, '_wpide_nl_token', true );
        if ( $saved && 'wpide_subscriber' === get_post_type( $id ) && hash_equals( $saved, $token ) ) {
            wp_update_post( array( 'ID' => $id, 'post_status' => 'publish' ) );
            delete_post_meta( $id, '_wpide_nl_token' );
            wp_safe_redirect( add_query_arg( 'wpide_nl', 'confirmed', home_url( '/' ) ) );
            exit;
        }
        return;
    }

    if ( empty( $_POST['wpide_nl_nonce'] ) ) {
        return;
    }

    $back = remove_query_arg( 'wpide_nl', wp_get_referer() ? wp_get_referer() : home_url( '/' ) );
    $done = function ( $status ) use ( $back ) {
        wp_safe_redirect( add_query_arg( 'wpide_nl', $status, $back ) . '#wpide-newsletter' );
        exit;
    };

    if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['wpide_nl_nonce'] ) ), 'wpide_nl' ) ) {
        $done( 'expired' );
    }
    if ( ! empty( $_POST['wpide_nl_hp'] ) ) {
        $done( 'check' );
    }

    $email = strtolower( sanitize_email( wp_unslash( isset( $_POST['wpide_nl_email'] ) ? $_POST['wpide_nl_email'] : '' ) ) );
    if ( ! is_email( $email ) ) {
        $done( 'invalid' );
    }

    $ip  = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
    $key = 'wpide_nl_rate_' . md5( wp_salt() . $ip );
    if ( (int) get_transient( $key ) >= 5 ) {
        $done( 'check' );
    }
    set_transient( $key, (int) get_transient( $key ) + 1, HOUR_IN_SECONDS );

    $existing = get_posts( array( 'post_type' => 'wpide_subscriber', 'post_status' => array( 'publish', 'draft' ), 'title' => $email, 'numberposts' => 1, 'fields' => 'ids', 'no_found_rows' => true ) );
    if ( $existing ) {
        $done( 'check' ); // Same answer for known addresses, so the list cannot be probed.
    }

    $token = wp_generate_password( 32, false );
    $id    = wp_insert_post( array( 'post_type' => 'wpide_subscriber', 'post_status' => 'draft', 'post_title' => $email, 'meta_input' => array( '_wpide_nl_token' => $token ) ) );

    if ( $id && ! is_wp_error( $id ) ) {
        $link = add_query_arg( array( 'wpide_nl_confirm' => $token, 'id' => $id ), home_url( '/' ) );
        wp_mail( $email, $wpide_nl_subject, "Thanks for signing up to " . wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) . ".\n\nPlease confirm your subscription by opening this link:\n$link\n\nIf you did not sign up, ignore this email." );
    }

    $done( 'check' );
} );

add_shortcode( 'wpide_newsletter', function ( $atts ) {
    $atts     = shortcode_atts( array( 'button' => 'Subscribe', 'placeholder' => 'Your email address' ), $atts, 'wpide_newsletter' );
    $messages = array(
        'check'     => 'Almost done! Please check your inbox and click the confirmation link.',
        'confirmed' => 'Thank you, your subscription is confirmed.',
        'invalid'   => 'Please enter a valid email address.',
        'expired'   => 'The form expired. Please try again.',
    );
    $status = isset( $_GET['wpide_nl'] ) ? sanitize_key( $_GET['wpide_nl'] ) : '';

    $html  = '<div id="wpide-newsletter" class="wpide-form wpide-newsletter">';
    $html .= isset( $messages[ $status ] ) ? '<p class="wpide-form-notice" role="status">' . esc_html( $messages[ $status ] ) . '</p>' : '';
    $html .= '<form method="post" action=""><label class="screen-reader-text" for="wpide-nl-email">Email address</label>';
    $html .= '<input type="email" id="wpide-nl-email" name="wpide_nl_email" required autocomplete="email" placeholder="' . esc_attr( $atts['placeholder'] ) . '"> ';
    $html .= '<span style="position:absolute;left:-9999px;" aria-hidden="true"><input type="text" name="wpide_nl_hp" tabindex="-1" autocomplete="off"></span>';
    $html .= wp_nonce_field( 'wpide_nl', 'wpide_nl_nonce', false, false );
    $html .= '<button type="submit">' . esc_html( $atts['button'] ) . '</button></form></div>';

    return $html;
} );

add_action( 'wpide_nl_cleanup', function () {
    $old = get_posts( array( 'post_type' => 'wpide_subscriber', 'post_status' => 'draft', 'date_query' => array( array( 'before' => '7 days ago' ) ), 'numberposts' => 200, 'fields' => 'ids', 'no_found_rows' => true ) );
    foreach ( $old as $id ) {
        wp_delete_post( $id, true );
    }
} );

add_action( 'admin_menu', function () {
    add_management_page( 'Subscribers', 'Subscribers', 'manage_options', 'wpide-subscribers', function () {
        $count = (int) wp_count_posts( 'wpide_subscriber' )->publish;
        $url   = wp_nonce_url( admin_url( 'admin-post.php?action=wpide_nl_export' ), 'wpide_nl_export' );
        echo '<div class="wrap"><h1>Subscribers</h1>';
        printf( '<p>%d confirmed subscriber(s).</p><p><a class="button button-primary" href="%s">Download CSV</a></p></div>', (int) $count, esc_url( $url ) );
    } );
} );

add_action( 'admin_post_wpide_nl_export', function () {
    if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( 'wpide_nl_export' ) ) {
        wp_die( 'Not allowed.' );
    }

    $ids = get_posts( array( 'post_type' => 'wpide_subscriber', 'post_status' => 'publish', 'numberposts' => -1, 'fields' => 'ids', 'no_found_rows' => true ) );

    nocache_headers();
    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename=subscribers-' . gmdate( 'Y-m-d' ) . '.csv' );

    $out = fopen( 'php://output', 'w' );
    fputcsv( $out, array( 'email', 'confirmed' ) );
    foreach ( $ids as $id ) {
        fputcsv( $out, array( ltrim( get_the_title( $id ), '=+-@' ), get_post_field( 'post_modified', $id ) ) );
    }
    fclose( $out );
    exit;
} );
```

## Notes

Confirmed subscribers are listed under Tools > Subscribers with a CSV export for your mailing tool, and unconfirmed sign-ups older than 7 days are removed daily.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Newsletter Sign-Up With Double Opt-In" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
