diff --git a/CHANGES b/CHANGES
index 3a81ca7bae4c1c4311f5c8f455ba2101a8787f17..7fdae7066b24eea8084d27dcbce9c80358b0fec7 100644
--- a/CHANGES
+++ b/CHANGES
@@ -104,6 +104,19 @@ Core:
  * libedit is no longer available as an embedded library and must be provided
    by the system.
 
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 15.4.0 to Asterisk 15.5.0 ------------
+------------------------------------------------------------------------------
+
+Core
+------------------
+ * Core bridging and, more specifically, bridge_softmix have been enhanced to
+   relay received frames of type TEXT or TEXT_DATA to all participants in a
+   softmix bridge.  res_pjsip_messaging and chan_pjsip have been enhanced to
+   take advantage of this so when res_pjsip_messaging receives an in-dialog
+   MESSAGE message from a user in a conference call, it's relayed to all
+   other participants in the call.
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 15.3.0 to Asterisk 15.4.0 ------------
 ------------------------------------------------------------------------------
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 16e1fb897c19b3baa4e532eca49141f72118c805..d7d34831ec0b9c296bc3f9c67a5ad11e2a48e8ac 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -36,6 +36,7 @@
 #include "asterisk/stream.h"
 #include "asterisk/test.h"
 #include "asterisk/vector.h"
+#include "asterisk/message.h"
 #include "bridge_softmix/include/bridge_softmix_internal.h"
 
 /*! The minimum sample rate of the bridge. */
@@ -1106,6 +1107,42 @@ cleanup:
 	ast_stream_topology_free(source_video);
 }
 
+/*!
+ * \internal
+ * \brief Determine what to do with a text frame.
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_text(struct ast_bridge *bridge,
+	struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+	if (DEBUG_ATLEAST(1)) {
+		struct ast_msg_data *msg = frame->data.ptr;
+		char frame_type[64];
+
+		ast_frame_type2str(frame->frametype, frame_type, sizeof(frame_type));
+
+		if (frame->frametype == AST_FRAME_TEXT_DATA) {
+			ast_log(LOG_DEBUG, "Received %s frame from '%s:%s': %s\n", frame_type,
+				ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),
+				ast_channel_name(bridge_channel->chan),
+				ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));
+		} else {
+			ast_log(LOG_DEBUG, "Received %s frame from '%s': %.*s\n", frame_type,
+				ast_channel_name(bridge_channel->chan), frame->datalen,
+				(char *)frame->data.ptr);
+		}
+	}
+
+	ast_bridge_queue_everyone_else(bridge, bridge_channel, frame);
+}
+
 /*!
  * \internal
  * \brief Determine what to do with a control frame.
@@ -1201,6 +1238,10 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha
 	case AST_FRAME_VIDEO:
 		softmix_bridge_write_video(bridge, bridge_channel, frame);
 		break;
+	case AST_FRAME_TEXT:
+	case AST_FRAME_TEXT_DATA:
+		softmix_bridge_write_text(bridge, bridge_channel, frame);
+		break;
 	case AST_FRAME_CONTROL:
 		res = softmix_bridge_write_control(bridge, bridge_channel, frame);
 		break;
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index dde7416c3180f4a0ed6671b5a91d9410f8c88e0a..b4eb711f796339a698ed21ce4311ac010436c269 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -61,6 +61,7 @@
 #include "asterisk/features_config.h"
 #include "asterisk/pickup.h"
 #include "asterisk/test.h"
+#include "asterisk/message.h"
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_session.h"
@@ -86,6 +87,7 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma
 static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,
 	struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,
 	const struct ast_channel *requestor, const char *data, int *cause);
+static int chan_pjsip_sendtext_data(struct ast_channel *ast, struct ast_msg_data *msg);
 static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);
 static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);
 static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
@@ -109,6 +111,7 @@ struct ast_channel_tech chan_pjsip_tech = {
 	.requester = chan_pjsip_request,
 	.requester_with_stream_topology = chan_pjsip_request_with_stream_topology,
 	.send_text = chan_pjsip_sendtext,
+	.send_text_data = chan_pjsip_sendtext_data,
 	.send_digit_begin = chan_pjsip_digit_begin,
 	.send_digit_end = chan_pjsip_digit_end,
 	.call = chan_pjsip_call,
@@ -125,7 +128,7 @@ struct ast_channel_tech chan_pjsip_tech = {
 	.queryoption = chan_pjsip_queryoption,
 	.func_channel_read = pjsip_acf_channel_read,
 	.get_pvt_uniqueid = chan_pjsip_get_uniqueid,
-	.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER
+	.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER | AST_CHAN_TP_SEND_TEXT_DATA
 };
 
 /*! \brief SIP session interaction functions */
@@ -2539,50 +2542,99 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma
 
 struct sendtext_data {
 	struct ast_sip_session *session;
-	char text[0];
+	struct ast_msg_data *msg;
 };
 
 static void sendtext_data_destroy(void *obj)
 {
 	struct sendtext_data *data = obj;
-	ao2_ref(data->session, -1);
+	ao2_cleanup(data->session);
+	ast_free(data->msg);
 }
 
-static struct sendtext_data* sendtext_data_create(struct ast_sip_session *session, const char *text)
+static struct sendtext_data* sendtext_data_create(struct ast_channel *chan,
+	struct ast_msg_data *msg)
 {
-	int size = strlen(text) + 1;
-	struct sendtext_data *data = ao2_alloc(sizeof(*data)+size, sendtext_data_destroy);
+	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
+	struct sendtext_data *data = ao2_alloc(sizeof(*data), sendtext_data_destroy);
 
 	if (!data) {
 		return NULL;
 	}
 
-	data->session = session;
+	data->msg = ast_msg_data_dup(msg);
+	if (!data->msg) {
+		ao2_cleanup(data);
+		return NULL;
+	}
+	data->session = channel->session;
 	ao2_ref(data->session, +1);
-	ast_copy_string(data->text, text, size);
+
 	return data;
 }
 
 static int sendtext(void *obj)
 {
-	RAII_VAR(struct sendtext_data *, data, obj, ao2_cleanup);
+	struct sendtext_data *data = obj;
 	pjsip_tx_data *tdata;
-
-	const struct ast_sip_body body = {
+	const char *body_text = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_BODY);
+	const char *content_type = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_CONTENT_TYPE);
+	char *sep;
+	struct ast_sip_body body = {
 		.type = "text",
 		.subtype = "plain",
-		.body_text = data->text
+		.body_text = body_text,
 	};
 
+	if (!ast_strlen_zero(content_type)) {
+		sep = strchr(content_type, '/');
+		if (sep) {
+			*sep = '\0';
+			body.type = content_type;
+			body.subtype = ++sep;
+		}
+	}
+
 	if (data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
 		ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
 			data->session->inv_session->cause,
 			pjsip_get_status_text(data->session->inv_session->cause)->ptr);
 	} else {
-		ast_debug(3, "Sending in dialog SIP message\n");
+		pjsip_from_hdr *hdr;
+		pjsip_name_addr *name_addr;
+		const char *from = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_FROM);
+		const char *to = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_TO);
+		int invalidate_tdata = 0;
 
 		ast_sip_create_request("MESSAGE", data->session->inv_session->dlg, data->session->endpoint, NULL, NULL, &tdata);
 		ast_sip_add_body(tdata, &body);
+
+		/*
+		 * If we have a 'from' in the msg, set the display name in the From
+		 * header to it.
+		 */
+		if (!ast_strlen_zero(from)) {
+			hdr = PJSIP_MSG_FROM_HDR(tdata->msg);
+			name_addr = (pjsip_name_addr *) hdr->uri;
+			pj_strdup2(tdata->pool, &name_addr->display, from);
+			invalidate_tdata = 1;
+		}
+
+		/*
+		 * If we have a 'to' in the msg, set the display name in the To
+		 * header to it.
+		 */
+		if (!ast_strlen_zero(to)) {
+			hdr = PJSIP_MSG_TO_HDR(tdata->msg);
+			name_addr = (pjsip_name_addr *) hdr->uri;
+			pj_strdup2(tdata->pool, &name_addr->display, to);
+			invalidate_tdata = 1;
+		}
+
+		if (invalidate_tdata) {
+			pjsip_tx_data_invalidate_msg(tdata);
+		}
+
 		ast_sip_send_request(tdata, data->session->inv_session->dlg, data->session->endpoint, NULL, NULL);
 	}
 
@@ -2590,14 +2642,22 @@ static int sendtext(void *obj)
 	pjsip_inv_dec_ref(data->session->inv_session);
 #endif
 
+	ao2_cleanup(data);
+
 	return 0;
 }
 
 /*! \brief Function called by core to send text on PJSIP session */
-static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)
+static int chan_pjsip_sendtext_data(struct ast_channel *ast, struct ast_msg_data *msg)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
-	struct sendtext_data *data = sendtext_data_create(channel->session, text);
+	struct sendtext_data *data = sendtext_data_create(ast, msg);
+
+	ast_debug(1, "Sending MESSAGE from '%s' to '%s:%s': %s\n",
+		ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),
+		ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),
+		ast_channel_name(ast),
+		ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));
 
 	if (!data) {
 		return -1;
@@ -2621,6 +2681,28 @@ static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)
 	return 0;
 }
 
+static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)
+{
+	struct ast_msg_data *msg;
+	int rc;
+	struct ast_msg_data_attribute attrs[] =
+	{
+		{
+			.type = AST_MSG_DATA_ATTR_BODY,
+			.value = (char *)text,
+		}
+	};
+
+	msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_UNKNOWN, attrs, ARRAY_LEN(attrs));
+	if (!msg) {
+		return -1;
+	}
+	rc = chan_pjsip_sendtext_data(ast, msg);
+	ast_free(msg);
+
+	return rc;
+}
+
 /*! \brief Convert SIP hangup causes to Asterisk hangup causes */
 static int hangup_sip2cause(int cause)
 {
diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c
index 59c8a1461ce5a64a59b147707360dcae36ab5a70..b62bae9deeb4421d5bf1a0f83a7da4ba3ca994a1 100644
--- a/funcs/func_frame_trace.c
+++ b/funcs/func_frame_trace.c
@@ -59,6 +59,7 @@
 					<enum name = "NULL" />
 					<enum name = "IAX" />
 					<enum name = "TEXT" />
+					<enum name = "TEXT_DATA" />
 					<enum name = "IMAGE" />
 					<enum name = "HTML" />
 					<enum name = "CNG" />
@@ -88,6 +89,7 @@ static struct {
 	{ AST_FRAME_NULL,   "NULL" },
 	{ AST_FRAME_IAX,   "IAX" },
 	{ AST_FRAME_TEXT,   "TEXT" },
+	{ AST_FRAME_TEXT_DATA,   "TEXT_DATA" },
 	{ AST_FRAME_IMAGE,   "IMAGE" },
 	{ AST_FRAME_HTML,   "HTML" },
 	{ AST_FRAME_CNG,   "CNG" },
@@ -391,6 +393,9 @@ static void print_frame(struct ast_frame *frame)
 	case AST_FRAME_TEXT:
 		ast_verbose("FrameType: TXT\n");
 		break;
+	case AST_FRAME_TEXT_DATA:
+		ast_verbose("FrameType: TXT_DATA\n");
+		break;
 	case AST_FRAME_IMAGE:
 		ast_verbose("FrameType: IMAGE\n");
 		break;
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 16f9aa8aef930b466e80fe221f326187a84bcd46..c865a8a32b2e51e336a47fcd21da19088e3963d1 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -592,6 +592,11 @@ struct ast_assigned_ids {
 	const char *uniqueid2;
 };
 
+/*!
+ * \brief Forward declaration
+ */
+struct ast_msg_data;
+
 /*!
  * \brief
  * Structure to describe a channel "technology", ie a channel driver
@@ -807,6 +812,9 @@ struct ast_channel_tech {
 	 * \retval -1 on error.
 	 */
 	int (*pre_call)(struct ast_channel *chan, const char *sub_args);
+
+	/*! \brief Display or transmit text with data*/
+	int (* const send_text_data)(struct ast_channel *chan, struct ast_msg_data *data);
 };
 
 /*! Kill the channel channel driver technology descriptor. */
@@ -934,6 +942,10 @@ enum {
 	 * world
 	 */
 	AST_CHAN_TP_INTERNAL = (1 << 2),
+	/*!
+	 * \brief Channels have this property if they implement send_text_data
+	 */
+	AST_CHAN_TP_SEND_TEXT_DATA = (1 << 3),
 };
 
 /*! \brief ast_channel flags */
@@ -2133,6 +2145,26 @@ int ast_set_write_format_interleaved_stereo(struct ast_channel *chan, struct ast
  */
 int ast_sendtext(struct ast_channel *chan, const char *text);
 
+/*!
+ * \brief Sends text to a channel in an ast_msg_data structure wrapper with ast_sendtext as fallback
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param chan channel to act upon
+ * \param msg ast_msg_data structure
+ *
+ * \details
+ * Write text to a display on a channel.  If the channel driver doesn't support the
+ * send_text_data callback. then the original send_text callback will be used if
+ * available.
+ *
+ * \note The channel does not need to be locked before calling this function.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg);
+
 /*!
  * \brief Receives a text character from a channel
  * \param chan channel to act upon
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index c3c0f88176cbafd31fa9a5ae54d0d4cb72bb3b55..542407ecc93dbe5fa50281d0be5db1c30f0464bc 100644
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -48,6 +48,7 @@ extern "C" {
  * \arg \b DTMF:   A DTMF digit, subclass is the digit
  * \arg \b IMAGE:  Image transport, mostly used in IAX
  * \arg \b TEXT:   Text messages and character by character (real time text)
+ * \arg \b TEXT_DATA:   Text messages in an ast_msg_data structure
  * \arg \b HTML:   URL's and web pages
  * \arg \b MODEM:  Modulated data encodings, such as T.38 and V.150
  * \arg \b IAX:    Private frame type for the IAX protocol
@@ -129,6 +130,8 @@ enum ast_frame_type {
 	AST_FRAME_BRIDGE_ACTION_SYNC,
 	/*! RTCP feedback (the subclass will contain the payload type) */
 	AST_FRAME_RTCP,
+	/*! Text message in an ast_msg_data structure */
+	AST_FRAME_TEXT_DATA,
 };
 #define AST_FRAME_DTMF AST_FRAME_DTMF_END
 
diff --git a/include/asterisk/message.h b/include/asterisk/message.h
index ae6533c46f65cc48e5f996aff4f3e979f3d12acc..826fa0ac3aeffc6bd32cca44ef0c91020be94ff7 100644
--- a/include/asterisk/message.h
+++ b/include/asterisk/message.h
@@ -407,6 +407,129 @@ void ast_msg_var_iterator_destroy(struct ast_msg_var_iterator *iter);
  */
 void ast_msg_var_unref_current(struct ast_msg_var_iterator *iter);
 
+
+/*! \defgroup ast_msg_data Enhanced Messaging
+ * @{
+ * \page Messaging Enhanced Messaging
+ *
+ * The basic messaging framework has a basic drawback... It can only pass
+ * a text string through the core.  This causes several issues:
+ * \li Only a content type of text/plain can be passed.
+ * \li If a softmix bridge is used, the original sender identity is lost.
+ *
+ * The Enhanced Messaging framework allows attributes, such as "From", "To"
+ * and "Content-Type" to be attached to the message by the incoming channel
+ * tech which can then be used by the outgoing channel tech to construct
+ * the appropriate technology-specific outgoing message.
+ */
+
+/*!
+ * \brief Structure used to transport an enhanced message through the frame core
+ * \since 13.22.0
+ * \since 15.5.0
+ */
+struct ast_msg_data;
+
+enum ast_msg_data_source_type {
+	AST_MSG_DATA_SOURCE_TYPE_UNKNOWN = 0,
+	AST_MSG_DATA_SOURCE_TYPE_T140,
+	AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG,
+	AST_MSG_DATA_SOURCE_TYPE_OUT_OF_DIALOG,
+	__AST_MSG_DATA_SOURCE_TYPE_LAST,
+};
+
+enum ast_msg_data_attribute_type {
+	AST_MSG_DATA_ATTR_TO = 0,
+	AST_MSG_DATA_ATTR_FROM,
+	AST_MSG_DATA_ATTR_CONTENT_TYPE,
+	AST_MSG_DATA_ATTR_BODY,
+	__AST_MSG_DATA_ATTR_LAST,
+};
+
+struct ast_msg_data_attribute {
+	enum ast_msg_data_attribute_type type;
+	char *value;
+};
+
+/*!
+ * \brief Allocates an ast_msg_data structure.
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param source The source type of the message
+ * \param attributes A pointer to an array of ast_msg_data_attribute structures
+ * \param count The number of elements in the array
+ *
+ * \return Pointer to msg structure or NULL on allocation failure.
+ *         Caller must call ast_free when done.
+ */
+struct ast_msg_data *ast_msg_data_alloc(enum ast_msg_data_source_type source,
+	struct ast_msg_data_attribute attributes[], size_t count);
+
+/*!
+ * \brief Clone an ast_msg_data structure
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param msg The message to clone
+ *
+ * \return New message structure or NULL if there was an allocation failure.
+ *         Caller must call ast_free when done.
+ */
+struct ast_msg_data *ast_msg_data_dup(struct ast_msg_data *msg);
+
+/*!
+ * \brief Get length of the structure
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param msg Pointer to ast_msg_data structure
+ *
+ * \return The length of the structure itself plus the dynamically allocated attribute buffer.
+ */
+size_t ast_msg_data_get_length(struct ast_msg_data *msg);
+
+/*!
+ * \brief Get "source type" from ast_msg_data
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param msg Pointer to ast_msg_data structure
+ *
+ * \return The source type field.
+ */
+enum ast_msg_data_source_type ast_msg_data_get_source_type(struct ast_msg_data *msg);
+
+/*!
+ * \brief Get attribute from ast_msg_data
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param msg Pointer to ast_msg_data structure
+ * \param attribute_type One of ast_msg_data_attribute_type
+ *
+ * \return The attribute or an empty string ("") if the attribute wasn't set.
+ */
+const char *ast_msg_data_get_attribute(struct ast_msg_data *msg,
+	enum ast_msg_data_attribute_type attribute_type);
+
+/*!
+ * \brief Queue an AST_FRAME_TEXT_DATA frame containing an ast_msg_data structure
+ * \since 13.22.0
+ * \since 15.5.0
+ *
+ * \param channel  The channel on which to queue the frame
+ * \param msg Pointer to ast_msg_data structure
+ *
+ * \retval -1 Error
+ * \retval  0 Success
+ */
+int ast_msg_data_queue_frame(struct ast_channel *channel, struct ast_msg_data *msg);
+
+/*!
+ *  @}
+ */
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index 3aac5eb254dfe0b9d88be1b2ea580ded2e22368a..eb4b9ad0e8f76ccc4eab3d817c990bb23638b9e1 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -56,6 +56,7 @@
 #include "asterisk/test.h"
 #include "asterisk/sem.h"
 #include "asterisk/stream.h"
+#include "asterisk/message.h"
 
 /*!
  * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge.
@@ -1055,6 +1056,20 @@ int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, st
 		return 0;
 	}
 
+	if (DEBUG_ATLEAST(1)) {
+		if (fr->frametype == AST_FRAME_TEXT) {
+			ast_log(LOG_DEBUG, "Queuing TEXT frame to '%s': %*.s\n", ast_channel_name(bridge_channel->chan),
+				fr->datalen, (char *)fr->data.ptr);
+		} else if (fr->frametype == AST_FRAME_TEXT_DATA) {
+			struct ast_msg_data *msg = fr->data.ptr;
+			ast_log(LOG_DEBUG, "Queueing TEXT_DATA frame from '%s' to '%s:%s': %s\n",
+				ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),
+				ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),
+				ast_channel_name(bridge_channel->chan),
+				ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));
+		}
+	}
+
 	AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
 	if (ast_alertpipe_write(bridge_channel->alert_pipe)) {
 		ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
@@ -2349,6 +2364,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
 	struct ast_frame *fr;
 	struct sync_payload *sync_payload;
 	int num;
+	struct ast_msg_data *msg;
 
 	ast_bridge_channel_lock(bridge_channel);
 
@@ -2381,6 +2397,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
 	AST_LIST_TRAVERSE_SAFE_END;
 
 	ast_bridge_channel_unlock(bridge_channel);
+
 	if (!fr) {
 		/*
 		 * Wait some to reduce CPU usage from a tight loop
@@ -2404,6 +2421,20 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
 		break;
 	case AST_FRAME_NULL:
 		break;
+	case AST_FRAME_TEXT:
+		ast_debug(1, "Sending TEXT frame to '%s': %*.s\n",
+			ast_channel_name(bridge_channel->chan), fr->datalen, (char *)fr->data.ptr);
+		ast_sendtext(bridge_channel->chan, fr->data.ptr);
+		break;
+	case AST_FRAME_TEXT_DATA:
+		msg = (struct ast_msg_data *)fr->data.ptr;
+		ast_debug(1, "Sending TEXT_DATA frame from '%s' to '%s:%s': %s\n",
+			ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),
+			ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),
+			ast_channel_name(bridge_channel->chan),
+			ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));
+		ast_sendtext_data(bridge_channel->chan, msg);
+		break;
 	default:
 		/* Assume that there is no mapped stream for this */
 		num = -1;
diff --git a/main/channel.c b/main/channel.c
index 815d5dbfe5b8316daadb93c2f1ef7e3f9aabe5e1..a23dfa10c7af2feb333cd6ba5abccb1a4fe40f10 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -73,6 +73,7 @@
 #include "asterisk/stasis_channels.h"
 #include "asterisk/max_forwards.h"
 #include "asterisk/stream.h"
+#include "asterisk/message.h"
 
 /*** DOCUMENTATION
  ***/
@@ -1492,6 +1493,7 @@ int ast_is_deferrable_frame(const struct ast_frame *frame)
 	case AST_FRAME_BRIDGE_ACTION_SYNC:
 	case AST_FRAME_CONTROL:
 	case AST_FRAME_TEXT:
+	case AST_FRAME_TEXT_DATA:
 	case AST_FRAME_IMAGE:
 	case AST_FRAME_HTML:
 		return 1;
@@ -2767,6 +2769,7 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay)
 				case AST_FRAME_VOICE:
 				case AST_FRAME_VIDEO:
 				case AST_FRAME_TEXT:
+				case AST_FRAME_TEXT_DATA:
 				case AST_FRAME_DTMF_BEGIN:
 				case AST_FRAME_DTMF_END:
 				case AST_FRAME_IMAGE:
@@ -4652,9 +4655,11 @@ char *ast_recvtext(struct ast_channel *chan, int timeout)
 	return buf;
 }
 
-int ast_sendtext(struct ast_channel *chan, const char *text)
+int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg)
 {
 	int res = 0;
+	const char *body = ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY);
+	const char *content_type = ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_CONTENT_TYPE);
 
 	ast_channel_lock(chan);
 	/* Stop if we're a zombie or need a soft hangup */
@@ -4663,35 +4668,76 @@ int ast_sendtext(struct ast_channel *chan, const char *text)
 		return -1;
 	}
 
-	if (ast_strlen_zero(text)) {
-		ast_channel_unlock(chan);
-		return 0;
-	}
-
 	CHECK_BLOCKING(chan);
-	if (ast_channel_tech(chan)->write_text && (ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_TEXT))) {
+	if (ast_channel_tech(chan)->write_text
+		&& (ast_strlen_zero(content_type) || strcasecmp(content_type, "text/plain") == 0)
+		&& (ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_TEXT))) {
 		struct ast_frame f;
+		size_t body_len = strlen(body) + 1;
 
+		/* Process as T.140 text (moved here from ast_sendtext() */
 		memset(&f, 0, sizeof(f));
-		f.frametype = AST_FRAME_TEXT;
 		f.src = "DIALPLAN";
-		f.mallocd = AST_MALLOCD_DATA;
-		f.datalen = strlen(text);
-		f.data.ptr = ast_strdup(text);
 		f.subclass.format = ast_format_t140;
-
+		f.frametype = AST_FRAME_TEXT;
+		f.datalen = body_len;
+		f.mallocd = AST_MALLOCD_DATA;
+		f.data.ptr = ast_strdup(body);
 		if (f.data.ptr) {
 			res = ast_channel_tech(chan)->write_text(chan, &f);
-			ast_frfree(&f);
+		} else {
+			res = -1;
 		}
-	} else if (ast_channel_tech(chan)->send_text) {
-		res = ast_channel_tech(chan)->send_text(chan, text);
+		ast_frfree(&f);
+	} else if ((ast_channel_tech(chan)->properties & AST_CHAN_TP_SEND_TEXT_DATA)
+		&& ast_channel_tech(chan)->send_text_data) {
+		/* Send enhanced message to a channel driver that supports it */
+		ast_debug(1, "Sending TEXT_DATA from '%s' to %s:%s %s\n",
+			ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),
+			ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),
+			ast_channel_name(chan), body);
+		res = ast_channel_tech(chan)->send_text_data(chan, msg);
+	} else if (ast_channel_tech(chan)->send_text
+		&& (ast_strlen_zero(content_type) || strcasecmp(content_type, "text/plain") == 0)) {
+		/* Send the body of an enhanced message to a channel driver that supports only a char str */
+		ast_debug(1, "Sending TEXT to %s: %s\n", ast_channel_name(chan), body);
+		res = ast_channel_tech(chan)->send_text(chan, body);
+	} else {
+		ast_debug(1, "Channel technology does not support sending text on channel '%s'\n",
+			ast_channel_name(chan));
+		res = -1;
 	}
 	ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING);
 	ast_channel_unlock(chan);
 	return res;
 }
 
+int ast_sendtext(struct ast_channel *chan, const char *text)
+{
+	struct ast_msg_data *msg;
+	int rc;
+	struct ast_msg_data_attribute attrs[] =
+	{
+		{
+			.type = AST_MSG_DATA_ATTR_BODY,
+			.value = (char *)text,
+		}
+	};
+
+	if (ast_strlen_zero(text)) {
+		return 0;
+	}
+
+	msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_UNKNOWN, attrs, ARRAY_LEN(attrs));
+	if (!msg) {
+		return -1;
+	}
+	rc = ast_sendtext_data(chan, msg);
+	ast_free(msg);
+
+	return rc;
+}
+
 int ast_senddigit_begin(struct ast_channel *chan, char digit)
 {
 	/* Device does not support DTMF tones, lets fake
diff --git a/main/frame.c b/main/frame.c
index dd47f42d0ad102e83e33f72800e4edd44eb11f67..383571f65dd349edc8241c65c6c2bd2f0270b340 100644
--- a/main/frame.c
+++ b/main/frame.c
@@ -593,6 +593,9 @@ void ast_frame_type2str(enum ast_frame_type frame_type, char *ftype, size_t len)
 	case AST_FRAME_TEXT:
 		ast_copy_string(ftype, "Text", len);
 		break;
+	case AST_FRAME_TEXT_DATA:
+		ast_copy_string(ftype, "Text Data", len);
+		break;
 	case AST_FRAME_IMAGE:
 		ast_copy_string(ftype, "Image", len);
 		break;
diff --git a/main/message.c b/main/message.c
index ac7965ea73013c56b715aacd15a9822b2bef5eb9..b7d14f1e8fc405bbdaa0ed4f892a1682817a3460 100644
--- a/main/message.c
+++ b/main/message.c
@@ -1348,6 +1348,148 @@ int ast_msg_send(struct ast_msg *msg, const char *to, const char *from)
 	return res;
 }
 
+/*!
+ * \brief Structure used to transport a message through the frame core
+ * \since 13.22.0
+ * \since 15.5.0
+ */
+struct ast_msg_data {
+	/*! The length of this structure plus the actual length of the allocated buffer */
+	size_t length;
+	enum ast_msg_data_source_type source;
+	/*! These are indices into the buffer where teh attribute starts */
+	int attribute_value_offsets[__AST_MSG_DATA_ATTR_LAST];
+	/*! The buffer containing the NULL separated attributes */
+	char buf[0];
+};
+
+#define ATTRIBUTE_UNSET -1
+
+struct ast_msg_data *ast_msg_data_alloc(enum ast_msg_data_source_type source,
+	struct ast_msg_data_attribute attributes[], size_t count)
+{
+	struct ast_msg_data *msg;
+	size_t len = sizeof(*msg);
+	size_t i;
+	size_t current_offset = 0;
+	enum ast_msg_data_attribute_type attr_type;
+
+	if (!attributes) {
+		ast_assert(attributes != NULL);
+		return NULL;
+	}
+
+	if (!count) {
+		ast_assert(count > 0);
+		return NULL;
+	}
+
+	/* Calculate the length required for the buffer */
+	for (i=0; i < count; i++) {
+		if (!attributes[i].value) {
+			ast_assert(attributes[i].value != NULL);
+			return NULL;
+		}
+		len += (strlen(attributes[i].value) + 1);
+	}
+
+	msg = ast_calloc(1, len);
+	if (!msg) {
+		return NULL;
+	}
+	msg->source = source;
+	msg->length = len;
+
+	/* Mark all of the attributes as unset */
+	for (attr_type = 0; attr_type < __AST_MSG_DATA_ATTR_LAST; attr_type++) {
+		msg->attribute_value_offsets[attr_type] = ATTRIBUTE_UNSET;
+	}
+
+	/* Set the ones we have and increment the offset */
+	for (i=0; i < count; i++) {
+		len = (strlen(attributes[i].value) + 1);
+		strcpy(msg->buf + current_offset, attributes[i].value); /* Safe */
+		msg->attribute_value_offsets[attributes[i].type] = current_offset;
+		current_offset += len;
+	}
+
+	return msg;
+}
+
+struct ast_msg_data *ast_msg_data_dup(struct ast_msg_data *msg)
+{
+	struct ast_msg_data *dest;
+
+	if (!msg) {
+		ast_assert(msg != NULL);
+		return NULL;
+	}
+
+	dest = ast_malloc(msg->length);
+	if (!dest) {
+		return NULL;
+	}
+	memcpy(dest, msg, msg->length);
+
+	return dest;
+}
+
+size_t ast_msg_data_get_length(struct ast_msg_data *msg)
+{
+	if (!msg) {
+		ast_assert(msg != NULL);
+		return 0;
+	}
+
+	return msg->length;
+}
+
+enum ast_msg_data_source_type ast_msg_data_get_source_type(struct ast_msg_data *msg)
+{
+	if (!msg) {
+		ast_assert(msg != NULL);
+		return AST_MSG_DATA_SOURCE_TYPE_UNKNOWN;
+	}
+
+	return msg->source;
+}
+
+const char *ast_msg_data_get_attribute(struct ast_msg_data *msg,
+	enum ast_msg_data_attribute_type attribute_type)
+{
+	if (!msg) {
+		ast_assert(msg != NULL);
+		return "";
+	}
+
+	if (msg->attribute_value_offsets[attribute_type] > ATTRIBUTE_UNSET) {
+		return msg->buf + msg->attribute_value_offsets[attribute_type];
+	}
+
+	return "";
+}
+
+int ast_msg_data_queue_frame(struct ast_channel *channel, struct ast_msg_data *msg)
+{
+	struct ast_frame f;
+
+	if (!channel) {
+		ast_assert(channel != NULL);
+		return -1;
+	}
+
+	if (!msg) {
+		ast_assert(msg != NULL);
+		return -1;
+	}
+
+	memset(&f, 0, sizeof(f));
+	f.frametype = AST_FRAME_TEXT_DATA;
+	f.data.ptr = msg;
+	f.datalen = msg->length;
+	return ast_queue_frame(channel, &f);
+}
+
 int ast_msg_tech_register(const struct ast_msg_tech *tech)
 {
 	const struct ast_msg_tech *match;
diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c
index cbaed83fb3aa6572a38f5945806ae126f4dd762f..d8628f5bd842ff4c32c1c3e807b633a708bf441f 100644
--- a/res/res_pjsip_messaging.c
+++ b/res/res_pjsip_messaging.c
@@ -75,6 +75,32 @@ static enum pjsip_status_code check_content_type(const pjsip_rx_data *rdata)
 	return res ? PJSIP_SC_OK : PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
 }
 
+/*!
+ * \internal
+ * \brief Checks to make sure the request has the correct content type.
+ *
+ * \details This module supports the following media types: "text/\*".
+ * Return unsupported otherwise.
+ *
+ * \param rdata The SIP request
+ */
+static enum pjsip_status_code check_content_type_any_text(const pjsip_rx_data *rdata)
+{
+	int res = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
+	pj_str_t text = { "text", 4};
+
+	/* We'll accept any text/ content type */
+	if (rdata->msg_info.msg->body && rdata->msg_info.msg->body->len
+		&& pj_stricmp(&rdata->msg_info.msg->body->content_type.type, &text) == 0) {
+		res = PJSIP_SC_OK;
+	} else if (rdata->msg_info.ctype
+		&& pj_stricmp(&rdata->msg_info.ctype->media.type, &text) == 0) {
+		res = PJSIP_SC_OK;
+	}
+
+	return res;
+}
+
 /*!
  * \internal
  * \brief Puts pointer past 'sip[s]:' string that should be at the
@@ -755,39 +781,98 @@ static pj_bool_t module_on_rx_request(pjsip_rx_data *rdata)
 
 static int incoming_in_dialog_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
-	char buf[MAX_BODY_SIZE];
 	enum pjsip_status_code code;
-	struct ast_frame f;
+	int rc;
 	pjsip_dialog *dlg = session->inv_session->dlg;
 	pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+	struct ast_msg_data *msg;
+	struct ast_party_caller *caller;
+	pjsip_name_addr *name_addr;
+	size_t from_len;
+	size_t to_len;
+	struct ast_msg_data_attribute attrs[4];
+	int pos = 0;
+	int body_pos;
 
 	if (!session->channel) {
 		send_response(rdata, PJSIP_SC_NOT_FOUND, dlg, tsx);
 		return 0;
 	}
 
-	if ((code = check_content_type(rdata)) != PJSIP_SC_OK) {
+	code = check_content_type_any_text(rdata);
+	if (code != PJSIP_SC_OK) {
 		send_response(rdata, code, dlg, tsx);
 		return 0;
 	}
 
-	if (print_body(rdata, buf, sizeof(buf)-1) < 1) {
-		/* invalid body size */
-		send_response(rdata, PJSIP_SC_REQUEST_ENTITY_TOO_LARGE, dlg, tsx);
+	caller = ast_channel_caller(session->channel);
+
+	name_addr = (pjsip_name_addr *) rdata->msg_info.from->uri;
+	from_len = pj_strlen(&name_addr->display);
+	if (from_len) {
+		attrs[pos].type = AST_MSG_DATA_ATTR_FROM;
+		from_len++;
+		attrs[pos].value = ast_alloca(from_len);
+		ast_copy_pj_str(attrs[pos].value, &name_addr->display, from_len);
+		pos++;
+	} else if (caller->id.name.valid && !ast_strlen_zero(caller->id.name.str)) {
+		attrs[pos].type = AST_MSG_DATA_ATTR_FROM;
+		attrs[pos].value = caller->id.name.str;
+		pos++;
+	}
+
+	name_addr = (pjsip_name_addr *) rdata->msg_info.to->uri;
+	to_len = pj_strlen(&name_addr->display);
+	if (to_len) {
+		attrs[pos].type = AST_MSG_DATA_ATTR_TO;
+		to_len++;
+		attrs[pos].value = ast_alloca(to_len);
+		ast_copy_pj_str(attrs[pos].value, &name_addr->display, to_len);
+		pos++;
+	}
+
+	attrs[pos].type = AST_MSG_DATA_ATTR_CONTENT_TYPE;
+	attrs[pos].value = ast_alloca(rdata->msg_info.msg->body->content_type.type.slen
+		+ rdata->msg_info.msg->body->content_type.subtype.slen + 2);
+	sprintf(attrs[pos].value, "%.*s/%.*s",
+		(int)rdata->msg_info.msg->body->content_type.type.slen,
+		rdata->msg_info.msg->body->content_type.type.ptr,
+		(int)rdata->msg_info.msg->body->content_type.subtype.slen,
+		rdata->msg_info.msg->body->content_type.subtype.ptr);
+	pos++;
+
+	body_pos = pos;
+	attrs[pos].type = AST_MSG_DATA_ATTR_BODY;
+	attrs[pos].value = ast_malloc(rdata->msg_info.msg->body->len + 1);
+	if (!attrs[pos].value) {
+		send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx);
 		return 0;
 	}
+	ast_copy_string(attrs[pos].value, rdata->msg_info.msg->body->data, rdata->msg_info.msg->body->len);
+	pos++;
 
-	ast_debug(3, "Received in dialog SIP message\n");
+	msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG, attrs, pos);
+	if (!msg) {
+		ast_free(attrs[body_pos].value);
+		send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx);
+		return 0;
+	}
 
-	memset(&f, 0, sizeof(f));
-	f.frametype = AST_FRAME_TEXT;
-	f.subclass.integer = 0;
-	f.offset = 0;
-	f.data.ptr = buf;
-	f.datalen = strlen(buf) + 1;
-	ast_queue_frame(session->channel, &f);
+	ast_debug(1, "Received in-dialog MESSAGE from '%s:%s': %s %s\n",
+		ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),
+		ast_channel_name(session->channel),
+		ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),
+		ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));
+
+	rc = ast_msg_data_queue_frame(session->channel, msg);
+	ast_free(attrs[body_pos].value);
+	ast_free(msg);
+	if (rc != 0) {
+		send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx);
+	} else {
+		send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx);
+	}
 
-	send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx);
 	return 0;
 }