diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index a4ad1cd784b38c59857f2a398b230acca103fb23..1f8b121e85f256a2b4206d41b84d6b75c7d5b3ea 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -20,16 +20,20 @@
 	<depend>pjproject</depend>
 	<depend>res_pjsip</depend>
 	<depend>res_pjsip_pubsub</depend>
+	<depend>res_pjsip_outbound_publish</depend>
 	<support_level>core</support_level>
  ***/
 
 #include "asterisk.h"
 
+#include <regex.h>
+
 #include <pjsip.h>
 #include <pjsip_simple.h>
 #include <pjlib.h>
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_outbound_publish.h"
 #include "asterisk/res_pjsip_pubsub.h"
 #include "asterisk/res_pjsip_body_generator_types.h"
 #include "asterisk/module.h"
@@ -42,6 +46,16 @@
 #define BODY_SIZE 1024
 #define EVENT_TYPE_SIZE 50
 
+/*!
+ * \brief The number of buckets to use for storing publishers
+ */
+#define PUBLISHER_BUCKETS 31
+
+/*!
+ * \brief Container of active outbound extension state publishers
+ */
+static struct ao2_container *publishers;
+
 /*!
  * \brief A subscription for extension state
  *
@@ -68,6 +82,29 @@ struct exten_state_subscription {
 	enum ast_presence_state last_presence_state;
 };
 
+/*!
+ * \brief An extension state publisher
+ *
+ */
+struct exten_state_publisher {
+	/*! Regular expression for context filtering */
+	regex_t context_regex;
+	/*! Regular expression for extension filtering */
+	regex_t exten_regex;
+	/*! Publish client to use for sending publish messages */
+	struct ast_sip_outbound_publish_client *client;
+	/*! Whether context filtering is active */
+	unsigned int context_filter;
+	/*! Whether extension filtering is active */
+	unsigned int exten_filter;
+	/*! The body type to use for this publisher - stored after the name */
+	char *body_type;
+	/*! The body subtype to use for this publisher - stored after the body type */
+	char *body_subtype;
+	/*! The name of this publisher */
+	char name[0];
+};
+
 #define DEFAULT_PRESENCE_BODY "application/pidf+xml"
 #define DEFAULT_DIALOG_BODY "application/dialog-info+xml"
 
@@ -77,6 +114,9 @@ static int subscription_established(struct ast_sip_subscription *sub);
 static void *get_notify_data(struct ast_sip_subscription *sub);
 static void to_ami(struct ast_sip_subscription *sub,
 		   struct ast_str **buf);
+static int publisher_start(struct ast_sip_outbound_publish *configuration,
+			   struct ast_sip_outbound_publish_client *client);
+static int publisher_stop(struct ast_sip_outbound_publish_client *client);
 
 struct ast_sip_notifier presence_notifier = {
 	.default_accept = DEFAULT_PRESENCE_BODY,
@@ -101,6 +141,12 @@ struct ast_sip_subscription_handler presence_handler = {
 	.notifier = &presence_notifier,
 };
 
+struct ast_sip_event_publisher_handler presence_publisher = {
+	.event_name = "presence",
+	.start_publishing = publisher_start,
+	.stop_publishing = publisher_stop,
+};
+
 struct ast_sip_subscription_handler dialog_handler = {
 	.event_name = "dialog",
 	.body_type = AST_SIP_EXTEN_STATE_DATA,
@@ -110,6 +156,12 @@ struct ast_sip_subscription_handler dialog_handler = {
 	.notifier = &dialog_notifier,
 };
 
+struct ast_sip_event_publisher_handler dialog_publisher = {
+	.event_name = "dialog",
+	.start_publishing = publisher_start,
+	.stop_publishing = publisher_stop,
+};
+
 static void exten_state_subscription_destructor(void *obj)
 {
 	struct exten_state_subscription *sub = obj;
@@ -490,31 +542,264 @@ static void to_ami(struct ast_sip_subscription *sub,
 			       exten_state_sub->last_exten_state));
 }
 
+/*!
+ * \brief Global extension state callback function
+ */
+static int exten_state_publisher_state_cb(const char *context, const char *exten, struct ast_state_cb_info *info, void *data)
+{
+	struct ao2_iterator publisher_iter;
+	struct exten_state_publisher *publisher;
+
+	publisher_iter = ao2_iterator_init(publishers, 0);
+	for (; (publisher = ao2_iterator_next(&publisher_iter)); ao2_ref(publisher, -1)) {
+		if ((publisher->context_filter && regexec(&publisher->context_regex, context, 0, NULL, 0)) ||
+		    (publisher->exten_filter && regexec(&publisher->exten_regex, exten, 0, NULL, 0))) {
+			continue;
+		}
+		/* This is a placeholder for additional code to come */
+	}
+	ao2_iterator_destroy(&publisher_iter);
+
+	return 0;
+}
+
+/*!
+ * \brief Hashing function for extension state publisher
+ */
+static int exten_state_publisher_hash(const void *obj, const int flags)
+{
+	const struct exten_state_publisher *object;
+	const char *key;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_KEY:
+		key = obj;
+		break;
+	case OBJ_SEARCH_OBJECT:
+		object = obj;
+		key = object->name;
+		break;
+	default:
+		ast_assert(0);
+		return 0;
+	}
+	return ast_str_hash(key);
+}
+
+/*!
+ * \brief Comparator function for extension state publisher
+ */
+static int exten_state_publisher_cmp(void *obj, void *arg, int flags)
+{
+	const struct exten_state_publisher *object_left = obj;
+	const struct exten_state_publisher *object_right = arg;
+	const char *right_key = arg;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		right_key = object_right->name;
+		/* Fall through */
+	case OBJ_SEARCH_KEY:
+		cmp = strcmp(object_left->name, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		/* Not supported by container. */
+		ast_assert(0);
+		return 0;
+	default:
+		cmp = 0;
+		break;
+	}
+	if (cmp) {
+		return 0;
+	}
+	return CMP_MATCH;
+}
+
+/*!
+ * \brief Destructor for extension state publisher
+ */
+static void exten_state_publisher_destroy(void *obj)
+{
+	struct exten_state_publisher *publisher = obj;
+
+	if (publisher->context_filter) {
+		regfree(&publisher->context_regex);
+	}
+
+	if (publisher->exten_filter) {
+		regfree(&publisher->exten_regex);
+	}
+
+	ao2_cleanup(publisher->client);
+}
+
+static int build_regex(regex_t *regex, const char *text)
+{
+	int res;
+
+	if ((res = regcomp(regex, text, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
+		size_t len = regerror(res, regex, NULL, 0);
+		char buf[len];
+		regerror(res, regex, buf, len);
+		ast_log(LOG_ERROR, "Could not compile regex '%s': %s\n", text, buf);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int publisher_start(struct ast_sip_outbound_publish *configuration, struct ast_sip_outbound_publish_client *client)
+{
+	struct exten_state_publisher *publisher;
+	size_t name_size;
+	size_t body_type_size;
+	size_t body_subtype_size;
+	char *body_subtype;
+	const char *body_full;
+	const char *body_type;
+	const char *name;
+	const char *context;
+	const char *exten;
+
+	name = ast_sorcery_object_get_id(configuration);
+
+	body_full = ast_sorcery_object_get_extended(configuration, "body");
+	if (ast_strlen_zero(body_full)) {
+		ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Body not set\n",
+			name);
+		return -1;
+	}
+
+	body_subtype = ast_strdupa(body_full);
+	body_type = strsep(&body_subtype, "/");
+	if (ast_strlen_zero(body_type) || ast_strlen_zero(body_subtype)) {
+		ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Body '%s' missing type or subtype\n",
+			name, body_full);
+		return -1;
+	}
+
+	name_size = strlen(name) + 1;
+	body_type_size = strlen(body_type) + 1;
+	body_subtype_size = strlen(body_subtype) + 1;
+
+	publisher = ao2_alloc_options(
+		sizeof(*publisher) + name_size + body_type_size + body_subtype_size,
+		exten_state_publisher_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!publisher) {
+		return -1;
+	}
+
+	ast_copy_string(publisher->name, name, name_size);
+	publisher->body_type = publisher->name + name_size;
+	ast_copy_string(publisher->body_type, body_type, body_type_size);
+	publisher->body_subtype = publisher->body_type + body_type_size;
+	ast_copy_string(publisher->body_subtype, body_subtype, body_subtype_size);
+
+	context = ast_sorcery_object_get_extended(configuration, "context");
+	if (!ast_strlen_zero(context)) {
+		if (build_regex(&publisher->context_regex, context)) {
+			ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Could not build context filter '%s'\n",
+				name, context);
+			ao2_ref(publisher, -1);
+			return -1;
+		}
+
+		publisher->context_filter = 1;
+	}
+
+	exten = ast_sorcery_object_get_extended(configuration, "exten");
+	if (!ast_strlen_zero(exten)) {
+		if (build_regex(&publisher->exten_regex, exten)) {
+			ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Could not build exten filter '%s'\n",
+				name, exten);
+			ao2_ref(publisher, -1);
+			return -1;
+		}
+
+		publisher->exten_filter = 1;
+	}
+
+	publisher->client = ao2_bump(client);
+
+	ao2_lock(publishers);
+	if (!ao2_container_count(publishers)) {
+		ast_extension_state_add(NULL, NULL, exten_state_publisher_state_cb, NULL);
+	}
+	ao2_link_flags(publishers, publisher, OBJ_NOLOCK);
+	ao2_unlock(publishers);
+
+	ao2_ref(publisher, -1);
+
+	return 0;
+}
+
+static int publisher_stop(struct ast_sip_outbound_publish_client *client)
+{
+	ao2_find(publishers, ast_sorcery_object_get_id(client), OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);
+	return 0;
+}
+
+static int unload_module(void)
+{
+	ast_sip_unregister_event_publisher_handler(&dialog_publisher);
+	ast_sip_unregister_subscription_handler(&dialog_handler);
+	ast_sip_unregister_event_publisher_handler(&presence_publisher);
+	ast_sip_unregister_subscription_handler(&presence_handler);
+
+	ast_extension_state_del(0, exten_state_publisher_state_cb);
+	ao2_cleanup(publishers);
+	publishers = NULL;
+
+	return 0;
+}
+
 static int load_module(void)
 {
-	CHECK_PJSIP_MODULE_LOADED();
+	CHECK_PJSIP_PUBSUB_MODULE_LOADED();
+
+	if (!ast_module_check("res_pjsip_outbound_publish.so")) {
+		ast_log(LOG_WARNING, "This module requires the 'res_pjsip_outbound_publish.so' module to be loaded\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	publishers = ao2_container_alloc(PUBLISHER_BUCKETS, exten_state_publisher_hash,
+		exten_state_publisher_cmp);
+	if (!publishers) {
+		ast_log(LOG_WARNING, "Unable to create container to store extension state publishers\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
 
 	if (ast_sip_register_subscription_handler(&presence_handler)) {
 		ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
 			presence_handler.event_name);
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (ast_sip_register_event_publisher_handler(&presence_publisher)) {
+		ast_log(LOG_WARNING, "Unable to register presence publisher %s\n",
+			presence_publisher.event_name);
+		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
 	if (ast_sip_register_subscription_handler(&dialog_handler)) {
 		ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
 			dialog_handler.event_name);
-		ast_sip_unregister_subscription_handler(&presence_handler);
+		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
-	return AST_MODULE_LOAD_SUCCESS;
-}
+	if (ast_sip_register_event_publisher_handler(&dialog_publisher)) {
+		ast_log(LOG_WARNING, "Unable to register presence publisher %s\n",
+			dialog_publisher.event_name);
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
 
-static int unload_module(void)
-{
-	ast_sip_unregister_subscription_handler(&dialog_handler);
-	ast_sip_unregister_subscription_handler(&presence_handler);
-	return 0;
+	return AST_MODULE_LOAD_SUCCESS;
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State Notifications",