Index: src/bp-core/admin/bp-core-schema.php
===================================================================
--- src/bp-core/admin/bp-core-schema.php
+++ src/bp-core/admin/bp-core-schema.php
@@ -304,6 +304,15 @@
 				KEY is_active (is_active)
 			) {$charset_collate};";
 
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_messages_meta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				message_id bigint(20) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY message_id (message_id),
+				KEY meta_key (meta_key)
+			) {$charset_collate};";
+
 	dbDelta( $sql );
 }
 
Index: src/bp-core/bp-core-update.php
===================================================================
--- src/bp-core/bp-core-update.php
+++ src/bp-core/bp-core-update.php
@@ -241,6 +241,11 @@
 		if ( $raw_db_version < 8311 ) {
 			bp_update_to_2_0_1();
 		}
+
+		// 2.0.1
+		if ( $raw_db_version < 9155 ) {
+			bp_update_to_2_2();
+		}
 	}
 
 	/** All done! *************************************************************/
@@ -390,6 +395,19 @@
 }
 
 /**
+ * 2.2.0 update routine.
+ *
+ * - Add messages meta table
+ *
+ * @since BuddyPress (2.0.0)
+ */
+function bp_update_to_2_2() {
+	if ( bp_is_active( 'messages' ) ) {
+		bp_core_install_private_messaging();
+	}
+}
+
+/**
  * Redirect user to BP's What's New page on first page load after activation.
  *
  * @since BuddyPress (1.7.0)
Index: src/bp-loader.php
===================================================================
--- src/bp-loader.php
+++ src/bp-loader.php
@@ -301,7 +301,7 @@
 		/** Versions **********************************************************/
 
 		$this->version    = '2.2-alpha';
-		$this->db_version = 8311;
+		$this->db_version = 9155;
 
 		/** Loading ***********************************************************/
 
Index: src/bp-messages/bp-messages-cache.php
===================================================================
--- src/bp-messages/bp-messages-cache.php
+++ src/bp-messages/bp-messages-cache.php
@@ -12,6 +12,29 @@
 // Exit if accessed directly
 if ( !defined( 'ABSPATH' ) ) exit;
 
+/**
+ * Slurp up metadata for a set of messages.
+ *
+ * It grabs all message meta associated with all of the messages passed in
+ * $message_ids and adds it to WP cache. This improves efficiency when using
+ * message meta within a loop context.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int|str|array $message_ids Accepts a single message_id, or a
+ *        comma-separated list or array of group ids.
+ */
+function bp_messages_update_meta_cache( $message_ids = false ) {
+	bp_update_meta_cache( array(
+		'object_ids' 	   => $message_ids,
+		'object_type' 	   => buddypress()->messages->id,
+		'cache_group'      => 'message_meta',
+		'object_column'    => 'message_id',
+		'meta_table' 	   => buddypress()->messages->table_name_meta,
+		'cache_key_prefix' => 'bp_messages_groupmeta'
+	) );
+}
+
 // List actions to clear super cached pages on, if super cache is installed
 add_action( 'messages_delete_thread',  'bp_core_clear_cache' );
 add_action( 'messages_send_notice',    'bp_core_clear_cache' );
Index: src/bp-messages/bp-messages-classes.php
===================================================================
--- src/bp-messages/bp-messages-classes.php
+++ src/bp-messages/bp-messages-classes.php
@@ -109,12 +109,11 @@
 	 *
 	 * @since BuddyPress (1.0.0)
 	 *
-	 * @param int $thread_id The message thread ID.
-	 * @param string $order The order to sort the messages. Either 'ASC' or 'DESC'.
+	 * @see BP_Messages_Thread::populate() for full description of parameters
 	 */
-	public function __construct( $thread_id = false, $order = 'ASC' ) {
+	public function __construct( $thread_id = false, $order = 'ASC', $args = array() ) {
 		if ( $thread_id ) {
-			$this->populate( $thread_id, $order );
+			$this->populate( $thread_id, $order, $args );
 		}
 	}
 
@@ -127,14 +126,24 @@
 	 *
 	 * @param int $thread_id The message thread ID.
 	 * @param string $order The order to sort the messages. Either 'ASC' or 'DESC'.
+	 * @param array $args {
+	 *     Array of arguments.
+	 *     @type bool $update_meta_cache Whether to pre-fetch metadata for
+	 *           queried message items. Default: true.
+	 * }
 	 */
-	public function populate( $thread_id, $order ) {
+	public function populate( $thread_id, $order = 'ASC', $args = array() ) {
 		global $wpdb, $bp;
 
 		if( 'ASC' != $order && 'DESC' != $order ) {
-			$order= 'ASC';
+			$order = 'ASC';
 		}
 
+		// merge $args with our defaults
+		$r = wp_parse_args( $args, array(
+			'update_meta_cache' => true
+		) );
+
 		$this->messages_order = $order;
 		$this->thread_id      = $thread_id;
 
@@ -153,6 +162,11 @@
 		if ( isset( $this->recipients[bp_loggedin_user_id()] ) ) {
 			$this->unread_count = $this->recipients[bp_loggedin_user_id()]->unread_count;
 		}
+
+		// Grab all message meta
+		if ( true === (bool) $r['update_meta_cache'] ) {
+			bp_messages_update_meta_cache( wp_list_pluck( $this->messages, 'id' ) );
+		}
 	}
 
 	/**
@@ -212,8 +226,8 @@
 		// Mark messages as deleted
 		$wpdb->query( $wpdb->prepare( "UPDATE {$bp->messages->table_name_recipients} SET is_deleted = 1 WHERE thread_id = %d AND user_id = %d", $thread_id, bp_loggedin_user_id() ) );
 
-		// Get the message id in order to pass to the action
-		$message_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_messages} WHERE thread_id = %d", $thread_id ) );
+		// Get the message ids in order to pass to the action
+		$message_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_messages} WHERE thread_id = %d", $thread_id ) );
 
 		// Check to see if any more recipients remain for this message
 		// if not, then delete the message from the database.
@@ -223,11 +237,16 @@
 			// Delete all the messages
 			$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->messages->table_name_messages} WHERE thread_id = %d", $thread_id ) );
 
+			// Delete message meta
+			foreach ( $message_ids as $message_id ) {
+				bp_messages_delete_meta( $message_id );
+			}
+
 			// Delete all the recipients
 			$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d", $thread_id ) );
 		}
 
-		do_action( 'messages_thread_deleted_thread', $message_id );
+		do_action( 'messages_thread_deleted_thread', $message_ids, $thread_id );
 
 		return true;
 	}
@@ -290,7 +309,11 @@
 
 		$threads = false;
 		foreach ( (array) $sorted_threads as $thread_id => $date_sent ) {
-			$threads[] = new BP_Messages_Thread( $thread_id );
+			$threads[] = new BP_Messages_Thread( $thread_id, 'ASC', array(
+				// need some feedback here...
+				// what are the chances a plugin will need the message meta during the inbox?
+				'update_meta_cache' => false
+			) );
 		}
 
 		return array( 'threads' => &$threads, 'total' => (int) $total_threads );
Index: src/bp-messages/bp-messages-functions.php
===================================================================
--- src/bp-messages/bp-messages-functions.php
+++ src/bp-messages/bp-messages-functions.php
@@ -335,3 +335,83 @@
 function messages_is_valid_thread( $thread_id ) {
 	return BP_Messages_Thread::is_valid( $thread_id );
 }
+
+/** Messages Meta *******************************************************/
+
+/**
+ * Delete metadata for a message.
+ *
+ * If $meta_key is false, this will delete all meta for the message ID.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see delete_metadata() for full documentation excluding $meta_type variable.
+ */
+function bp_messages_delete_meta( $message_id, $meta_key = false, $meta_value = false, $delete_all = false ) {
+	global $wpdb;
+
+	// Legacy - if no meta_key is passed, delete all for the item
+	if ( empty( $meta_key ) ) {
+		$keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->messagemeta} WHERE message_id = %d", $message_id ) );
+
+		// With no meta_key, ignore $delete_all
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'message', $message_id, $key, $meta_value, $delete_all );
+	}
+
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get a piece of group metadata.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see get_metadata() for full documentation excluding $meta_type variable.
+ */
+function bp_messages_get_meta( $message_id, $meta_key = '', $single = true ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = get_metadata( 'message', $message_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Update a piece of message metadata.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see update_metadata() for full documentation excluding $meta_type variable.
+ */
+function bp_messages_update_meta( $message_id, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = update_metadata( 'message', $message_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of message metadata.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see add_metadata() for full documentation excluding $meta_type variable.
+ */
+function bp_message_add_meta( $message_id, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = add_metadata( 'message', $message_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
Index: src/bp-messages/bp-messages-loader.php
===================================================================
--- src/bp-messages/bp-messages-loader.php
+++ src/bp-messages/bp-messages-loader.php
@@ -90,22 +90,27 @@
 		$global_tables = array(
 			'table_name_notices'    => $bp->table_prefix . 'bp_messages_notices',
 			'table_name_messages'   => $bp->table_prefix . 'bp_messages_messages',
-			'table_name_recipients' => $bp->table_prefix . 'bp_messages_recipients'
+			'table_name_recipients' => $bp->table_prefix . 'bp_messages_recipients',
+			'table_name_meta'       => $bp->table_prefix . 'bp_messages_meta',
 		);
 
+		// Metadata tables for messaging component
+		$meta_tables = array(
+			'message' => $bp->table_prefix . 'bp_messages_meta',
+		);
+
+		$this->autocomplete_all = defined( 'BP_MESSAGES_AUTOCOMPLETE_ALL' );
+
 		// All globals for messaging component.
 		// Note that global_tables is included in this array.
-		$globals = array(
+		parent::setup_globals( array(
 			'slug'                  => BP_MESSAGES_SLUG,
 			'has_directory'         => false,
 			'notification_callback' => 'messages_format_notifications',
 			'search_string'         => __( 'Search Messages...', 'buddypress' ),
-			'global_tables'         => $global_tables
-		);
-
-		$this->autocomplete_all = defined( 'BP_MESSAGES_AUTOCOMPLETE_ALL' );
-
-		parent::setup_globals( $globals );
+			'global_tables'         => $global_tables,
+			'meta_tables'           => $meta_tables
+		) );
 	}
 
 	/**
Index: src/bp-messages/bp-messages-template.php
===================================================================
--- src/bp-messages/bp-messages-template.php
+++ src/bp-messages/bp-messages-template.php
@@ -1405,6 +1405,8 @@
  *           Default: if viewing a thread, the thread ID will be parsed from
  *           the URL (bp_action_variable( 0 )).
  *     @type string $order 'ASC' or 'DESC'. Default: 'ASC'.
+ *     @type bool $update_meta_cache Whether to pre-fetch metadata for
+ *           queried message items. Default: true.
  * }
  * @return bool True if there are messages to display, otherwise false.
  */
@@ -1412,15 +1414,20 @@
 	global $thread_template;
 
 	$r = bp_parse_args( $args, array(
-		'thread_id' => false,
-		'order'     => 'ASC'
+		'thread_id'         => false,
+		'order'             => 'ASC',
+		'update_meta_cache' => true,
 	), 'thread_has_messages' );
 
 	if ( empty( $r['thread_id'] ) && bp_is_messages_component() && bp_is_current_action( 'view' ) ) {
 		$r['thread_id'] = (int) bp_action_variable( 0 );
 	}
 
-	$thread_template = new BP_Messages_Thread_Template( $r['thread_id'], $r['order'] );
+	// Set up extra args
+	$extra_args = $r;
+	unset( $extra_args['thread_id'], $extra_args['order'] );
+
+	$thread_template = new BP_Messages_Thread_Template( $r['thread_id'], $r['order'], $extra_args );
 
 	return $thread_template->has_messages();
 }
@@ -1834,6 +1841,42 @@
  * @todo Add Messages meta?
  */
 function bp_messages_embed() {
-	add_filter( 'embed_post_id', 'bp_get_message_thread_id' );
+	add_filter( 'embed_post_id',         'bp_get_the_thread_message_id' );
+	add_filter( 'bp_embed_get_cache',    'bp_embed_message_cache',      10, 3 );
+	add_action( 'bp_embed_update_cache', 'bp_embed_message_save_cache', 10, 3 );
+}
+add_action( 'thread_loop_start', 'bp_messages_embed' );
+
+/**
+ * Fetch a private message item's cached embeds.
+ *
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_messages_embed()}.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param string $cache An empty string passed by BP_Embed::parse_oembed() for
+ *        functions like this one to filter.
+ * @param int $id The ID of the activity item.
+ * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
+ * @return mixed The cached embeds for this activity item.
+ */
+function bp_embed_message_cache( $cache, $id, $cachekey ) {
+	return bp_messages_get_meta( $id, $cachekey );
+}
+
+/**
+ * Set a private message item's embed cache.
+ *
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_messages_embed()}.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param string $cache An empty string passed by BP_Embed::parse_oembed() for
+ *        functions like this one to filter.
+ * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
+ * @param int $id The ID of the activity item.
+ * @return bool True on success, false on failure.
+ */
+function bp_embed_message_save_cache( $cache, $cachekey, $id ) {
+	bp_messages_update_meta( $id, $cachekey, $cache );
 }
\ No newline at end of file
-add_action( 'messages_box_loop_start', 'bp_messages_embed' );
Index: src/bp-templates/bp-legacy/buddypress-functions.php
===================================================================
--- src/bp-templates/bp-legacy/buddypress-functions.php
+++ src/bp-templates/bp-legacy/buddypress-functions.php
@@ -1298,6 +1298,8 @@
 
 		bp_thread_has_messages( array( 'thread_id' => (int) $_REQUEST['thread_id'] ) );
 
+		bp_thread_the_message();
+
 		if ( $thread_template->message_count % 2 == 1 ) {
 			$class = 'odd';
 		} else {
