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.
Opens WPIDE on
Code
$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 add this snippet
- Install WPIDE on your WordPress site (it is free) and open Code Snippets.
- Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
- It is added inactive: review the code and settings, then switch it on.