Index: src/bp-activity/bp-activity-notifications.php
===================================================================
--- src/bp-activity/bp-activity-notifications.php	(revision 10870)
+++ src/bp-activity/bp-activity-notifications.php	(working copy)
@@ -54,6 +54,11 @@
 			$group_name = bp_get_current_group_name();
 		}
 
+		$unsubscribe_args = array(
+			'user_id'           => $receiver_user_id,
+			'notification_type' => $email_type,
+		);
+
 		$args = array(
 			'tokens' => array(
 				'activity'         => $activity,
@@ -62,6 +67,7 @@
 				'mentioned.url'    => $message_link,
 				'poster.name'      => $poster_name,
 				'receiver-user.id' => $receiver_user_id,
+				'unsubscribe' 	   => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 			),
 		);
 
@@ -113,6 +119,12 @@
 
 		// Send an email if the user hasn't opted-out.
 		if ( 'no' != bp_get_user_meta( $original_activity->user_id, 'notification_activity_new_reply', true ) ) {
+
+			$unsubscribe_args = array(
+				'user_id'           => $original_activity->user_id,
+				'notification_type' => 'activity-comment',
+			);
+
 			$args = array(
 				'tokens' => array(
 					'comment.id'                => $comment_id,
@@ -121,6 +133,7 @@
 					'original_activity.user_id' => $original_activity->user_id,
 					'poster.name'               => $poster_name,
 					'thread.url'                => esc_url( $thread_link ),
+					'unsubscribe'               => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 				),
 			);
 
@@ -155,6 +168,12 @@
 
 		// Send an email if the user hasn't opted-out.
 		if ( 'no' != bp_get_user_meta( $parent_comment->user_id, 'notification_activity_new_reply', true ) ) {
+
+			$unsubscribe_args = array(
+				'user_id'           => $parent_comment->user_id,
+				'notification_type' => 'activity-comment-author',
+			);
+
 			$args = array(
 				'tokens' => array(
 					'comment.id'             => $comment_id,
@@ -163,6 +182,7 @@
 					'parent-comment-user.id' => $parent_comment->user_id,
 					'poster.name'            => $poster_name,
 					'thread.url'             => esc_url( $thread_link ),
+					'unsubscribe'            => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 				),
 			);
 
@@ -178,7 +198,7 @@
 		 * @param int                  $comment_id     ID for the newly received comment.
 		 * @param int                  $commenter_id   ID of the user who made the comment.
 		 * @param array                $params         Arguments used with the original activity comment.
- 		 */
+		 */
 		do_action( 'bp_activity_sent_reply_to_reply_notification', $parent_comment, $comment_id, $commenter_id, $params );
 	}
 }
Index: src/bp-core/admin/bp-core-admin-schema.php
===================================================================
--- src/bp-core/admin/bp-core-admin-schema.php	(revision 10870)
+++ src/bp-core/admin/bp-core-admin-schema.php	(working copy)
@@ -517,7 +517,7 @@
 	);
 
 	$emails       = bp_email_get_schema();
-	$descriptions = bp_email_get_type_schema();
+	$descriptions = bp_email_get_type_schema( 'description' );
 
 	// Add these emails to the database.
 	foreach ( $emails as $id => $email ) {
Index: src/bp-core/bp-core-actions.php
===================================================================
--- src/bp-core/bp-core-actions.php	(revision 10870)
+++ src/bp-core/bp-core-actions.php	(working copy)
@@ -110,3 +110,6 @@
 
 // Activation redirect.
 add_action( 'bp_activation', 'bp_add_activation_redirect' );
+
+// Email unsubscribe.
+add_action( 'bp_get_request_unsubscribe', 'bp_email_unsubscribe_handler' );
Index: src/bp-core/bp-core-filters.php
===================================================================
--- src/bp-core/bp-core-filters.php	(revision 10870)
+++ src/bp-core/bp-core-filters.php	(working copy)
@@ -1028,7 +1028,6 @@
 	$tokens['recipient.email']     = '';
 	$tokens['recipient.name']      = '';
 	$tokens['recipient.username']  = '';
-	$tokens['unsubscribe']         = site_url( 'wp-login.php' );
 
 
 	// Who is the email going to?
@@ -1045,16 +1044,22 @@
 		}
 
 		if ( $user_obj ) {
-			// Unsubscribe link.
-			$tokens['unsubscribe'] = esc_url( sprintf(
-				'%s%s/notifications/',
-				bp_core_get_user_domain( $user_obj->ID ),
-				function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings'
-			) );
 			$tokens['recipient.username'] = $user_obj->user_login;
-		}
+			if ( bp_is_active( 'settings' ) && empty( $tokens['unsubscribe'] ) ) {
+ 			    $tokens['unsubscribe'] = esc_url( sprintf(
+ 			        '%s%s/notifications/',
+ 			        bp_core_get_user_domain( $user_obj->ID ),
+ 			        bp_get_settings_slug()
+ 			    ) );
+			}
+ 		}
 	}
 
+	// Set default unsubscribe link if not passed.
+    if ( empty( $tokens['unsubscribe'] ) ) {
+         $tokens['unsubscribe'] = site_url( 'wp-login.php' );
+    }
+
 	// Email preheader.
 	$post = $email->get_post_object();
 	if ( $post ) {
Index: src/bp-core/bp-core-functions.php
===================================================================
--- src/bp-core/bp-core-functions.php	(revision 10870)
+++ src/bp-core/bp-core-functions.php	(working copy)
@@ -3369,26 +3369,341 @@
  * Get a list of emails for populating email type taxonomy terms.
  *
  * @since 2.5.1
+ * @since 2.6.0 $field argument added.
  *
- * @return array
+ * @param string $field Optional; defaults to "description" for backwards compatibility. Other values: "all".
+ * @return array {
+ *     The array of email types and their schema.
+ *
+ *     @type string $description The description of the action which causes this to trigger.
+ *     @type array  $unsubscribe {
+ *         Replacing this with false indicates that a user cannot unsubscribe from this type.
+ *
+ *         @type string $meta_key The meta_key used to toggle the email setting for this notification.
+ *         @type string $message  The message shown when the user has successfully unsubscribed.
+ *     }
  */
-function bp_email_get_type_schema() {
-	return array(
-		'activity-comment'                   => __( 'A member has replied to an activity update that the recipient posted.', 'buddypress' ),
-		'activity-comment-author'            => __( 'A member has replied to a comment on an activity update that the recipient posted.', 'buddypress' ),
-		'activity-at-message'                => __( 'Recipient was mentioned in an activity update.', 'buddypress' ),
-		'groups-at-message'                  => __( 'Recipient was mentioned in a group activity update.', 'buddypress' ),
-		'core-user-registration'             => __( 'Recipient has registered for an account.', 'buddypress' ),
-		'core-user-registration-with-blog'   => __( 'Recipient has registered for an account and site.', 'buddypress' ),
-		'friends-request'                    => __( 'A member has sent a friend request to the recipient.', 'buddypress' ),
-		'friends-request-accepted'           => __( 'Recipient has had a friend request accepted by a member.', 'buddypress' ),
-		'groups-details-updated'             => __( "A group's details were updated.", 'buddypress' ),
-		'groups-invitation'                  => __( 'A member has sent a group invitation to the recipient.', 'buddypress' ),
-		'groups-member-promoted'             => __( "Recipient's status within a group has changed.", 'buddypress' ),
-		'groups-membership-request'          => __( 'A member has requested permission to join a group.', 'buddypress' ),
-		'messages-unread'                    => __( 'Recipient has received a private message.', 'buddypress' ),
-		'settings-verify-email-change'       => __( 'Recipient has changed their email address.', 'buddypress' ),
-		'groups-membership-request-accepted' => __( 'Recipient had requested to join a group, which was accepted.', 'buddypress' ),
-		'groups-membership-request-rejected' => __( 'Recipient had requested to join a group, which was rejected.', 'buddypress' ),
+function bp_email_get_type_schema( $field = 'description' ) {
+	$activity_comment = array(
+		'description'	=> __( 'A member has replied to an activity update that the recipient posted.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_activity_new_reply',
+			'message'	=> __( 'You will no longer receive emails when someone replies to an update or comment you posted.', 'buddypress' ),
+			),
 	);
+
+	$activity_comment_author = array(
+		'description'	=> __( 'A member has replied to a comment on an activity update that the recipient posted.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_activity_new_reply',
+			'message'	=> __( 'You will no longer receive emails when someone replies to an update or comment you posted.', 'buddypress' ),
+			),
+	);
+
+	$activity_at_message = array(
+		'description'	=> __( 'Recipient was mentioned in an activity update.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_activity_new_mention',
+			'message'	=> __( 'You will no longer receive emails when someone mentions you in an update.', 'buddypress' ),
+		),
+	);
+
+	$groups_at_message = array(
+		'description'	=> __( 'Recipient was mentioned in a group activity update.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_activity_new_mention',
+			'message'	=> __( 'You will no longer receive emails when someone mentions you in an update.', 'buddypress' ),
+		),
+	);
+
+	$core_user_registration = array(
+		'description'	=> __( 'Recipient has registered for an account.', 'buddypress' ),
+		'unsubscribe'	=> false,
+	);
+
+	$core_user_registration_with_blog = array(
+		'description'	=> __( 'Recipient has registered for an account and site.', 'buddypress' ),
+		'unsubscribe'	=> false,
+	);
+
+	$friends_request = array(
+		'description'	=> __( 'A member has sent a friend request to the recipient.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_friends_friendship_request',
+			'message'	=> __( 'You will no longer receive emails when someone sends you a friend request.', 'buddypress' ),
+		),
+	);
+
+	$friends_request_accepted = array(
+		'description'	=> __( 'Recipient has had a friend request accepted by a member.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_friends_friendship_accepted',
+			'message'	=> __( 'You will no longer receive emails when someone accepts your friendship request.', 'buddypress' ),
+		),
+	);
+
+	$groups_details_updated = array(
+		'description'	=> __( "A group's details were updated.", 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_groups_group_updated',
+			'message'	=> __( 'You will no longer receive emails when one of your groups is updated.', 'buddypress' ),
+		),
+	);
+
+	$groups_details_updated = array(
+		'description'	=> __( "A group's details were updated.", 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_groups_group_updated',
+			'message'	=> __( 'You will no longer receive emails when one of your groups is updated.', 'buddypress' ),
+		),
+	);
+
+	$groups_invitation = array(
+		'description'	=> __( 'A member has sent a group invitation to the recipient.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_groups_invite',
+			'message'	=> __( 'You will no longer receive emails when you are invited to join a group.', 'buddypress' ),
+		),
+	);
+
+	$groups_member_promoted = array(
+		'description'	=> __( "Recipient's status within a group has changed.", 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_groups_admin_promotion',
+			'message'	=> __( 'You will no longer receive emails when you have been promoted in a group.', 'buddypress' ),
+		),
+	);
+
+	$groups_member_promoted = array(
+		'description'	=> __( "Recipient's status within a group has changed.", 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_groups_admin_promotion',
+			'message'	=> __( 'You will no longer receive emails when you have been promoted in a group.', 'buddypress' ),
+		),
+	);
+
+	$groups_membership_request = array(
+		'description'	=> __( 'A member has requested permission to join a group.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_groups_membership_request',
+			'message'	=> __( 'You will no longer receive emails when someone requests to be a member of your group.', 'buddypress' ),
+		),
+	);
+
+	$messages_unread = array(
+		'description'	=> __( 'Recipient has received a private message.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_messages_new_message',
+			'message'	=> __( 'You will no longer receive emails when someone sends you a message.', 'buddypress' ),
+		),
+	);
+
+	$settings_verify_email_change = array(
+		'description'	=> __( 'Recipient has changed their email address.', 'buddypress' ),
+		'unsubscribe'	=> false,
+	);
+
+	$groups_membership_request_accepted = array(
+		'description'	=> __( 'Recipient had requested to join a group, which was accepted.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_membership_request_completed',
+			'message'	=> __( 'You will no longer receive emails when your request to join a group has been accepted or denied.', 'buddypress' ),
+		),
+	);
+
+	$groups_membership_request_rejected = array(
+		'description'	=> __( 'Recipient had requested to join a group, which was rejected.', 'buddypress' ),
+		'unsubscribe'	=> array(
+			'meta_key'	=> 'notification_membership_request_completed',
+			'message'	=> __( 'You will no longer receive emails when your request to join a group has been accepted or denied.', 'buddypress' ),
+		),
+	);
+
+	$types = array(
+		'activity-comment'                   => $activity_comment,
+		'activity-comment-author'            => $activity_comment_author,
+		'activity-at-message'                => $activity_at_message,
+		'groups-at-message'                  => $groups_at_message,
+		'core-user-registration'             => $core_user_registration,
+		'core-user-registration-with-blog'   => $core_user_registration_with_blog,
+		'friends-request'                    => $friends_request,
+		'friends-request-accepted'           => $friends_request_accepted,
+		'groups-details-updated'             => $groups_details_updated,
+		'groups-invitation'                  => $groups_invitation,
+		'groups-member-promoted'             => $groups_member_promoted,
+		'groups-membership-request'          => $groups_membership_request,
+		'messages-unread'                    => $messages_unread,
+		'settings-verify-email-change'       => $settings_verify_email_change,
+		'groups-membership-request-accepted' => $groups_membership_request_accepted,
+		'groups-membership-request-rejected' => $groups_membership_request_rejected,
+	);
+
+	if ( $field !== 'all' ) {
+		return wp_list_pluck( $types, $field );
+	} else {
+		return $types;
+	}
 }
+
+/**
+ * Handles unsubscribing user from notification emails.
+ *
+ * @since 2.6.0
+ */
+function bp_email_unsubscribe_handler() {
+	$emails = bp_email_get_type_schema();
+
+	$raw_user_id = ! empty( $_GET['uid'] ) ? absint( $_GET['uid'] ) : 0;
+	$to_check    = ! empty( $_GET['nn'] ) ? sanitize_text_field( $_GET['nn'] ) : '';
+	$unsub_type  = ! empty( $_GET['nt'] ) ? sanitize_text_field( $_GET['nt'] ) : '';
+
+	$check_args = array(
+		'uid' => $raw_user_id,
+		'nt'  => $unsub_type,
+	);
+
+	$check = bp_hash_array( $check_args );
+
+	// Check required values.
+	if ( ! $raw_user_id || ! $unsub_type || ! $to_check || ! array_key_exists( $unsub_type, $emails ) ) {
+		$redirect_to = site_url( 'wp-login.php' );
+		$result_msg  = __( 'Something has gone wrong.', 'buddypress' );
+		$unsub_msg   = __( 'Please log in and go to your settings to unsubscribe from notification emails.', 'buddypress' );
+
+	// Check valid hash.
+	} elseif ( ! hash_equals( $check, $to_check ) ) {
+		$redirect_to = site_url( 'wp-login.php' );
+		$result_msg  = __( 'Something has gone wrong.', 'buddypress' );
+		$unsub_msg   = __( 'Please log in and go to your settings to unsubscribe from notification emails.', 'buddypress' );
+
+	// Don't let authenticated users unsubscribe other users' email notifications.
+	} elseif ( is_user_logged_in() && get_current_user_id() !== $raw_user_id ) {
+		$redirect_to = sprintf(
+			'%s%s/notifications/',
+			bp_core_get_user_domain( get_current_user_id() ),
+			function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings'
+		);
+
+		$result_msg = __( 'Something has gone wrong.', 'buddypress' );
+		$unsub_msg  = __( 'Please go to your settings to unsubscribe from notification emails.', 'buddypress' );
+
+	} else {
+		$redirect_to =  sprintf(
+			'%s%s/notifications/',
+			bp_core_get_user_domain( $raw_user_id ),
+			function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings'
+		);
+
+		// Unsubscribe.
+		// djpaultodo - raw_user_id not safe. verify hash match will validate the user id.
+		$meta_key        = $emails[ $unsub_type ]['unsubscribe']['meta_key'];
+		$is_unsubscribed = bp_update_user_meta( $raw_user_id, $meta_key, 'no' );
+
+		$result_msg = $emails[ $unsub_type ]['unsubscribe']['message'];
+		$unsub_msg  = __( 'You can change this or any other email notification preferences in your email settings.', 'buddypress' );
+	}
+
+	$message = sprintf(
+		'%1$s <a href="%2$s">%3$s</a>',
+		$result_msg,
+		esc_url( $redirect_to ),
+		$unsub_msg
+	);
+
+	bp_core_add_message( $message );
+	bp_core_redirect( bp_core_get_user_domain( $raw_user_id ) );
+	exit;
+}
+
+/**
+ * Creates unsubscribe link for notification emails.
+ *
+ * @since
+ *
+ * @param string 	$redirect_to The URL to which the unsubscribe query string is appended.
+ * @param array 	$args 	     {
+ *
+ *    Used to build unsubscribe query string.
+ *
+ *    @type string $notification_type Which notification type is being sent.
+ *    @type string $user_id           The ID of the user to whom the notification is sent.
+ *    @type string $redirect_to       Optional. The url to which the user will be redirected. Default is the activity directory.
+ *
+ * }
+ * @return string The unsubscribe link.
+ */
+function bp_email_get_unsubscribe_link( $args = null ) {
+	// djpaultodo - review/tweak function.
+	$bp_emails_type_schema = bp_email_get_type_schema();
+
+	if ( ! is_array( $args ) || ! isset( $args['notification_type'] ) || ! array_key_exists( $args['notification_type'], $bp_emails_type_schema ) ) {
+		return site_url( 'wp-login.php' );
+	}
+
+	$notification_type = $unsubscribe_args['nt'] = esc_attr( $args['notification_type'] );
+	$user_id = $unsubscribe_args['uid'] = (int) $args['user_id'];
+
+	// If the activity type is not unsubscribable (ununsubscribable), return false.
+	if ( empty( $bp_emails_type_schema[ $notification_type ]['unsubscribe'] ) ) {
+		return false;
+	}
+
+	$redirect_to = ( isset( $args['redirect_to'] ) ) ? isset( $args['redirect_to'] ) : bp_get_activity_directory_permalink();
+
+	$check = bp_hash_array( $unsubscribe_args );
+
+	$unsubscribe_args['nn'] = $check;
+	$unsubscribe_args['action'] = 'unsubscribe';
+
+	$unsubscribe_link = esc_url_raw( add_query_arg( $unsubscribe_args, $redirect_to ) );
+
+	/**
+	 * Filters the unsubscribe link.
+	 *
+	 * @since
+	 *
+	 * @param string 	$redirect_to 	URL to which the unsubscribe query string is appended.
+	 * @param array 	$args 	        Used to build unsubscribe query string.
+	 */
+	return apply_filters( 'bp_email_get_unsubscribe_link', $unsubscribe_link, $redirect_to, $args );
+}
+
+/**
+ * Get a persistent salt for email unsubscribe links, not dependent on salts in wp-config.php.
+ *
+ * @since 2.6.0
+ *
+ * @return string
+ */
+function bp_email_get_salt() {
+	$salt = bp_get_option( 'bp-emails-unsubscribe-salt', null );
+
+	if ( is_null( $salt ) ) {
+		$salt = base64_encode( wp_generate_password( 12, true, true ) ); 
+		bp_add_option( 'bp-emails-unsubscribe-salt', $salt );
+	}
+
+	return $salt;
+}
+
+/**
+ * Get a function that makes a checkable hash for the given args.
+ *
+ * @param  array 	$args 	Any array.
+ *
+ * @return string
+ *
+ */
+function bp_hash_array( $args ) {
+	$salt = bp_email_get_salt();
+
+	// the key order shouldn't matter when checking two arrays, so the array is sorted
+	ksort( $args );
+
+	$string = ( is_array( $args ) ) ? implode( ':', $args ) : (string) $args;
+
+	// should this return a substring like the nonce does? It's quite long.
+	$check = hash_hmac( 'sha1', $string, $salt );
+
+	return $check;
+}
Index: src/bp-friends/bp-friends-notifications.php
===================================================================
--- src/bp-friends/bp-friends-notifications.php	(revision 10870)
+++ src/bp-friends/bp-friends-notifications.php	(working copy)
@@ -32,6 +32,11 @@
 		return;
 	}
 
+	$unsubscribe_args = array(
+		'user_id'           => $friend_id,
+		'notification_type' => 'friends-request',
+	);
+
 	$args = array(
 		'tokens' => array(
 			'friend-requests.url' => esc_url( bp_core_get_user_domain( $friend_id ) . bp_get_friends_slug() . '/requests/' ),
@@ -40,6 +45,7 @@
 			'initiator.id'        => $initiator_id,
 			'initiator.url'       => esc_url( bp_core_get_user_domain( $initiator_id ) ),
 			'initiator.name'      => bp_core_get_user_displayname( $initiator_id ),
+			'unsubscribe'         => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 		),
 	);
 	bp_send_email( 'friends-request', $friend_id, $args );
@@ -63,6 +69,11 @@
 		return;
 	}
 
+	$unsubscribe_args = array(
+		'user_id'           => $initiator_id,
+		'notification_type' => 'friends-request-accepted',
+	);
+
 	$args = array(
 		'tokens' => array(
 			'friend.id'      => $friend_id,
@@ -70,6 +81,7 @@
 			'friend.name'    => bp_core_get_user_displayname( $friend_id ),
 			'friendship.id'  => $friendship_id,
 			'initiator.id'   => $initiator_id,
+			'unsubscribe'	   => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 		),
 	);
 	bp_send_email( 'friends-request-accepted', $initiator_id, $args );
Index: src/bp-groups/bp-groups-notifications.php
===================================================================
--- src/bp-groups/bp-groups-notifications.php	(revision 10870)
+++ src/bp-groups/bp-groups-notifications.php	(working copy)
@@ -68,6 +68,11 @@
 			continue;
 		}
 
+		$unsubscribe_args = array(
+			'user_id'           => $user_id,
+			'notification_type' => 'groups-details-updated',
+		);
+
 		$args = array(
 			'tokens' => array(
 				'changed_text' => $changed_text,
@@ -75,6 +80,7 @@
 				'group.id'     => $group_id,
 				'group.url'    => esc_url( bp_get_group_permalink( $group ) ),
 				'group.name'   => $group->name,
+				'unsubscribe'  => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 			),
 		);
 		bp_send_email( 'groups-details-updated', (int) $user_id, $args );
@@ -115,7 +121,7 @@
 			'item_id'           => $group_id,
 			'secondary_item_id' => $requesting_user_id,
 			'component_name'    => buddypress()->groups->id,
-			'component_action'  => 'new_membership_request'
+			'component_action'  => 'new_membership_request',
 		) );
 	}
 
@@ -124,6 +130,11 @@
 		return;
 	}
 
+	$unsubscribe_args = array(
+		'user_id'           => $admin_id,
+		'notification_type' => 'groups-membership-request',
+	);
+
 	$group = groups_get_group( array( 'group_id' => $group_id ) );
 	$args  = array(
 		'tokens' => array(
@@ -136,6 +147,7 @@
 			'profile.url'          => esc_url( bp_core_get_user_domain( $requesting_user_id ) ),
 			'requesting-user.id'   => $requesting_user_id,
 			'requesting-user.name' => bp_core_get_user_displayname( $requesting_user_id ),
+			'unsubscribe'          => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 		),
 	);
 	bp_send_email( 'groups-membership-request', (int) $admin_id, $args );
@@ -184,8 +196,25 @@
 	);
 
 	if ( ! empty( $accepted ) ) {
+
+		$unsubscribe_args = array(
+			'user_id'           => $requesting_user_id,
+			'notification_type' => 'groups-membership-request-accepted',
+		);
+
+		$args['tokens']['unsubscribe'] = esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) );
+
 		bp_send_email( 'groups-membership-request-accepted', (int) $requesting_user_id, $args );
+
 	} else {
+
+		$unsubscribe_args = array(
+			'user_id'           => $requesting_user_id,
+			'notification_type' => 'groups-membership-request-rejected',
+		);
+
+		$args['tokens']['unsubscribe'] = esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) );
+
 		bp_send_email( 'groups-membership-request-rejected', (int) $requesting_user_id, $args );
 	}
 }
@@ -226,6 +255,11 @@
 		return;
 	}
 
+	$unsubscribe_args = array(
+		'user_id'           => $user_id,
+		'notification_type' => 'groups-member-promoted',
+	);
+
 	$group = groups_get_group( array( 'group_id' => $group_id ) );
 	$args  = array(
 		'tokens' => array(
@@ -235,6 +269,7 @@
 			'group.name'  => $group->name,
 			'promoted_to' => $promoted_to,
 			'user.id'     => $user_id,
+			'unsubscribe' => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 		),
 	);
 	bp_send_email( 'groups-member-promoted', (int) $user_id, $args );
@@ -277,6 +312,12 @@
 	}
 
 	$invited_link = bp_core_get_user_domain( $invited_user_id ) . bp_get_groups_slug();
+
+	$unsubscribe_args = array(
+		'user_id'           => $invited_user_id,
+		'notification_type' => 'groups-invitation',
+	);
+
 	$args         = array(
 		'tokens' => array(
 			'group'        => $group,
@@ -286,6 +327,7 @@
 			'inviter.url'  => bp_core_get_user_domain( $inviter_user_id ),
 			'inviter.id'   => $inviter_user_id,
 			'invites.url'  => esc_url( $invited_link . '/invites/' ),
+			'unsubscribe'  => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 		),
 	);
 	bp_send_email( 'groups-invitation', (int) $invited_user_id, $args );
Index: src/bp-messages/bp-messages-notifications.php
===================================================================
--- src/bp-messages/bp-messages-notifications.php	(revision 10870)
+++ src/bp-messages/bp-messages-notifications.php	(working copy)
@@ -58,6 +58,11 @@
 			continue;
 		}
 
+		$unsubscribe_args = array(
+			'user_id'           => $recipient->user_id,
+			'notification_type' => 'messages-unread',
+		);
+
 		$args = array(
 			'tokens' => array(
 				'usermessage' => wp_strip_all_tags( stripslashes( $message ) ),
@@ -64,6 +69,7 @@
 				'message.url' => esc_url( bp_core_get_user_domain( $recipient->user_id ) . bp_get_messages_slug() . '/view/' . $thread_id . '/' ),
 				'sender.name' => $sender_name,
 				'usersubject' => sanitize_text_field( stripslashes( $subject ) ),
+				'unsubscribe' => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
 			),
 		);
 		bp_send_email( 'messages-unread', $ud, $args );
Index: tests/phpunit/testcases/admin/functions.php
===================================================================
--- tests/phpunit/testcases/admin/functions.php	(revision 10870)
+++ tests/phpunit/testcases/admin/functions.php	(working copy)
@@ -252,7 +252,7 @@
 			'hide_empty' => false,
 		) );
 
-		$correct_descriptions = bp_email_get_type_schema();
+		$correct_descriptions = bp_email_get_type_schema( 'description' );
 		foreach ( $d_terms as $d_term ) {
 			$correct_description = $correct_descriptions[ $d_term->slug ];
 			$this->assertSame( $correct_description, $d_term->description );
Index: tests/phpunit/testcases/core/functions.php
===================================================================
--- tests/phpunit/testcases/core/functions.php	(revision 10870)
+++ tests/phpunit/testcases/core/functions.php	(working copy)
@@ -721,4 +721,32 @@
 		$result      = bp_email_add_link_color_to_template( $content, 'template', 'add-content' );
 		$this->assertContains( $link_color, $result );
 	}
+
+	public function test_bp_hash_array_order_shouldnt_matter(){
+		$array_order_1_hash	= bp_hash_array(
+			array(
+				'a'	=> 'x',
+				'b'	=> 'y',
+				'c'	=> 'z',
+			)
+		);
+
+		$array_order_2_hash	= bp_hash_array( 
+			array(
+				'c'	=> 'z',
+				'a'	=> 'x',
+				'b'	=> 'y',
+			)
+		);
+
+		$this->assertTrue( hash_equals( $array_order_1_hash, $array_order_2_hash ) );
+	}
+
+	public function test_bp_email_get_salt(){
+		$this->assertTrue( is_null( bp_get_option( 'bp-emails-unsubscribe-salt', null ) ) );
+
+		$salt = bp_email_get_salt();
+
+		$this->assertTrue( $salt === bp_get_option( 'bp-emails-unsubscribe-salt', null ) );
+	}
 }
