Redirect user after successful email change

You need to do the processing of the form before headers are sent downstream. Otherwise, if you do it while the body is loading (the way you have it now), then redirecting after a successful email change will result in an error.

A lot of people would use the init action for this; but personally, I like to use template_redirect to hook form processing to. It comes a little later and then the $post object is loaded if you need any data from that.

Here’s how I would approach it:

add_action( 'template_redirect', 'my_user_email_update' );
function my_user_email_update() {

    global $error;

    $error = false;

    if ( is_user_logged_in() && isset( $_POST['email'] ) ) {

        // ALWAYS sanitize untrusted input!
        $new_email = sanitize_email( $_POST['email'] );

        // Get the user info for validating that the email is changing
        $user = wp_get_current_user();

        // check if user is really updating the value
        if ( $user->user_email != $new_email ) {

            // Is the new email actually an email address?
            if ( ! is_email( $new_email ) ) {
                $error="Please input a valid email address.";
            }

            // check if email is free to use
            if ( email_exists( $new_email ) ) {
                // email already taken
                $error="That e-mail address is not available.";

            } else {

                $_POST['user_id'] = $current_user->ID; // This doesn't make sense.

                $success = wp_update_user( array( 'ID'=>$user->ID, 'user_email'=>$new_email ) );

                send_confirmation_on_profile_email();

                // You could redirect here and pass a query string to indicate successful update
            }   
        } else {
            //same email
            $error="The email you entered is the same as your current email.";
        }
    }
}

Some things to note:

  1. ALWAYS sanitize input. Instead of retrieving $_POST['email'] each time, put it in a variable and sanitize it. Then use the sanitized result in the variable throughout.
  2. You validate with email_exists() which is good. You should also validate that the posted value is actually a valid email with is_email().
  3. I’m not sure what the $_POST['user_id'] = $current_user->ID is for. Neither of these make sense, so I’m not sure what was intended in that line.
  4. As I mentioned, if you want to redirect, you need to do this before headers are sent, as I have in this example. I operated under the assumption that if there is any type of error, you won’t redirect but rather display the error message on the existing form. So there is a global $error and any error result loads into that variable. I initialized it as false so you can check it in the area of your form and if it’s not false, display the result.