WordPress: Send Admin Notification when User Updates Specific Profile Fields


I’ve had some requests in the past from clients who want to be notified when WordPress users change information in their user profile, easy enough for the most part. However, a recent client asked to be notified when specific information was changed, mainly, contact related info. This would include their email address, their phone number and their mailing address. No big deal, or so I thought…

Rename Post

fig 1.0 send mail

The Issue

This simple idea turned out to be not-so-straightforward. Setting up the conditions for notification on email change were easy: compare $old_user_data->user_email to $user->user_email*, if the values are not equal, send an email. Great!

*more about profile updates in the codex.

However, when using this method with the phone field and any of the address fields $old_user_data returns null. Why?! After some exhaustive searching, I found this little bit of information that explained everything:

$old_user_data is an object containing only the basic WP_User fields, but none of the additional user keys (first_name, last_name, etc. are not part of this object).

Ahhhh, ok. Well at least that explains it. So, what to do now? I thought about if for a bit, but no easy solution came to mind. I also wanted to implement a solution that was native to WordPress, without using jQuery etc. I posted the situation to the Advanced WordPress Facebook Group, which if you’re not involved in, you should be!

Within a matter of minutes I had multiple ideas coming in from awesome WordPress folks. One solution in particular stood out to me, it was a suggestion from Nick Ciske, to store the user meta in a transient, and then compare the data. A transient? I had never heard of this before so I looked it up in the codex, behold, awesome: Transient API.

I was super excited, this is what I needed! I went to bed happy knowing that I had a solution to integrate the next day. When I awoke the next morning I had a message from Nick, he had personally taken the code sample I had posted on GitHub and coded the transient portions for me! Wow…what and awesome and selfless thing to do :)

The Solution

Since this is nowhere to be found on the interwebs, below is the solution I came up with including Nick’s awesome code for setting up the transient metadata. There is an email to be sent for each of the following conditions, when they are true:

1. A user’s email is updated
2. A user’s phone number is updated
3. Any of the address related fields are updated

/* ===========================================
    Send Emails when User Profile Changes

function sr_user_profile_update_email( $user_id, $old_user_data ) {

  $user = get_userdata( $user_id );
  if($old_user_data->user_email != $user->user_email) {
    $admin_email = "";
    $message = sprintf( __( 'This user has updated their profile on the SchoolRise USA Staff Member site.' ) ) . "\r\n\r\n";
    $message .= sprintf( __( 'Display Name: %s' ), $user->display_name ). "\r\n\r\n";
    $message .= sprintf( __( 'Old Email: %s' ), $old_user_data->user_email ). "\r\n\r\n";
    $message .= sprintf( __( 'New Email: %s' ), $user->user_email ). "\r\n\r\n";
    wp_mail( $admin_email, sprintf( __( '[Staff Member Site] User Profile Update' ), get_option('blogname') ), $message );


add_action( 'profile_update', 'sr_user_profile_update_email', 10, 2 );

function sr_user_profile_update_phone( $user_id, $old_user_data ) {

  $old_user_data = get_transient( 'sr_old_user_data_' . $user_id );
  $user = get_userdata( $user_id );

  if($old_user_data->phone != $user->phone) {
    $admin_email = "";
    $message = sprintf( __( 'This user has updated their profile on the SchoolRise USA Staff Member site.' ) ) . "\r\n\r\n";
    $message .= sprintf( __( 'Display Name: %s' ), $user->display_name ). "\r\n\r\n";
    $message .= sprintf( __( 'Old Phone: %s' ), $old_user_data->phone ). "\r\n\r\n";
    $message .= sprintf( __( 'New Phone: %s' ), $user->phone ). "\r\n\r\n";
    wp_mail( $admin_email, sprintf( __( '[Staff Member Site] User Profile Update' ), get_option('blogname') ), $message );


add_action( 'profile_update', 'sr_user_profile_update_phone', 10, 2 );

function sr_user_profile_update_address( $user_id, $old_user_data ) {

  $old_user_data = get_transient( 'sr_old_user_data_' . $user_id );
  $user = get_userdata( $user_id );

  if($old_user_data->street != $user->street or $old_user_data->city != $user->city or $old_user_data->state != $user->state or $old_user_data->zip != $user->zip) {

    $admin_email = "";
    $message = sprintf( __( 'This user has updated their profile on the SchoolRise USA Staff Member site.' ) ) . "\r\n\r\n";
    $message .= sprintf( __( 'Display Name: %s' ), $user->display_name ). "\r\n\r\n";
    $message .= sprintf( __( 'Old Address: %s, %s, %s %s' ), $old_user_data->street, $old_user_data->city, $old_user_data->state, $old_user_data->zip ). "\r\n\r\n";
    $message .= sprintf( __( 'New Address: %s, %s, %s %s' ), $user->street, $user->city, $user->state, $user->zip ). "\r\n\r\n";
    wp_mail( $admin_email, sprintf( __( '[Staff Member Site] User Profile Update' ), get_option('blogname') ), $message );


add_action( 'profile_update', 'sr_user_profile_update_address', 10, 2 );

// Save old user data and meta for later comparison for non-standard fields (phone, address etc.)
function sr_old_user_data_transient(){

  $user_id = get_current_user_id();
  $user_data = get_userdata( $user_id );
  $user_meta = get_user_meta( $user_id );

  foreach( $user_meta as $key=>$val ){
    $user_data->data->$key = current($val);

  // 1 hour should be sufficient
  set_transient( 'sr_old_user_data_' . $user_id, $user_data->data, 60 * 60 );

add_action('show_user_profile', 'sr_old_user_data_transient');

// Cleanup when done
function sr_old_user_data_cleanup( $user_id, $old_user_data ){
  delete_transient( 'sr_old_user_data_' . $user_id );
add_action( 'profile_update', 'sr_old_user_data_cleanup', 1000, 2 );


In conclusion, code is awesome. Code that works is more awesome. And people in the WordPress community are the most awesome of them all. Thanks again, Nick!


  1. how to send a notification from admin to user?

    • Chris Perryman

      So sorry, I didn’t see your comment! So essentially, you’d want to replicate these lines:

      wp_mail( $admin_email, sprintf( __( '[Staff Member Site] User Profile Update' ), get_option('blogname') ), $message );

      And swap out the $admin_email for $user_email, but you’d need to define that variable first. You should be able to do something like:

      $user = get_userdata( $user_id );
      $user_email = $user->user_email;
  2. Anyone ever have luck using this with a WooCommerce address? Here’s what I have:

    function sr_user_profile_update_address( $user_id, $old_user_data ) {

    $old_user_data = get_transient( ‘sr_old_user_data_’ . $user_id );
    $user = get_userdata( $user_id );

    if($old_user_data->billing_first_name != $user->billing_first_name or billing_last_name != $user->billing_last_name or billing_company != $user->billing_company or $old_user_data->billing_address_1 != $user->billing_address_1 or billing_address_2 != $user->billing_address_2 or $old_user_data->billing_city != $user->billing_city or $old_user_data->billing_state != $user->billing_state or $old_user_data->billing_postcode != $user->billing_postcode or $old_user_data->billing_phone != $user->billing_phone) {

    $admin_email = “TEST@TEST.COM”;
    $headers[] = ‘From: Test Sender ‘;
    $message = sprintf( __( ‘This user has updated their profile on the site.’ ) ) . “\r\n\r\n”;
    $message .= sprintf( __( ‘Display Name: %s’ ), $user->display_name ). “\r\n\r\n”;
    $message .= sprintf( __( ‘Old Address: %s, %s, %s, %s, %s, %s, %s, %s %s’ ), $old_user_data->billing_first_name, $old_user_data->billing_last_name, $old_user_data->billing_company, $old_user_data->billing_address_1, $old_user_data->billing_address_2, $old_user_data->billing_city, $old_user_data->billing_state, $old_user_data->billing_postcode, $old_user_data->billing_phone ). “\r\n\r\n”;
    $message .= sprintf( __( ‘New Address: %s, %s, %s, %s, %s, %s, %s %s, %s’ ), $user->billing_first_name, $user->billing_last_name, $user->billing_company, $user->billing_address_1, $user->billing_address_2, $user->billing_city, $user->billing_state, $user->billing_postcode, $user->billing_phone ). “\r\n\r\n”;
    wp_mail( $admin_email, sprintf( __( ‘[PAPGA] User Profile Update’ ), get_option(‘blogname’) ), $headers, $message );

    add_action( ‘profile_update’, ‘sr_user_profile_update_address’, 10, 2 );

    It works – to some degree. If I go in to the My Account > Addresses area of WooCommerce and change the address, nothing happens. But if I just go into the My Account > Account Details and simply save what’s already there, the new address info gets mailed out.

    Any ideas on what might work with Woo?

  3. seb


    I was looking for a solution to save metadata before user edit its profile and I found your article.

    However, you code seems to be not accurate!

    Imagine, the user open its profile page but wait maybe 5hours before hitting the edit button with the new data, your transient will be deleted !

    You never know when the user will update data.

    So you should use another method!


    • Chris Perryman

      You have to weigh whether or not you think that would be the case. In this situation, we decided one hour would be enough time. You can always adjust that number and set yours for longer…or another option for you if you’re concerned, would be to implement some JS and have the page “timeout” after a certain period of time (much like online banking etc.) – then prompt the user that the page is going to be refreshed.

    • seb


      I think transient is not a good solution for that kind of need, because on the documentation they said that the amount of time after the transient expire is not accurate, it is only the maximum time possible but the transient can be deleted before that time (maybe 1second just after it has been set).

      So it is not accurate, regardless the time of expiration you put.

      I think transient should be used only for code that is going to execute almost directly after transients have been set…


  4. Steven

    Hi Chris,

    Looks like an awesome script. I tried it in my functions.php of my child theme but nothing happens. I’m using WordPress Registration Form ( for users to register btw. Any tips on how to handle this?



    • Chris Perryman

      I don’t think the registration method should matter. You changed the email fields in the code to your email address, correct? Then, you went to a profile and changed their phone number (for example)?

  5. Ram


    I embedded the code into my WordPress function.php file and added the admin email, but admin doesn’t receive any email upon user profile update. I am the newbie in this.

    I am using BuddyPress.

    Is there something that i am missing.

    Thanks in advance

    • Chris Perryman

      Hi Ram! This solution is for the default user profile fields etc. Take a look at this post for info on using with BuddyPress:

    • Ram

      Thanks for the reply. Right now I am using, but the problem that it have is that it doesn’t show the old email or the old details which have been changed. Like:

      Old E-mail changed to New E-mail
      Old Name changed to New Name

      Instead, it shows the details, not the changes.

      Thanks in advance.

    • Chris Perryman

      Ah, I think I understand what you’re saying. So you would probably need to combine the BuddyPress function you are using with the transient method from this post – that would “save” the “old data” and then send an email if that data doesn’t match the “new data” – in the code above you can see where we are referencing “old phone”, “new phone” etc.

      Does that help? I’m not sure how comfortable you are with programming – I would need to spend time testing/modifying to get you a definitive solution, but I don’t have time outside of client work at the moment.

{ Respond }

Leave a response