changeset 19189:1afdeb1cae62

Added push-notification plugin
author Michael M Slusarz <michael.slusarz@dovecot.fi>
date Tue, 22 Sep 2015 22:33:41 -0600
parents 3a942625aaa0
children 55e6910aed18
files configure.ac dovecot-config.in.in src/plugins/Makefile.am src/plugins/push-notification/Makefile.am src/plugins/push-notification/push-notification-driver-dlog.c src/plugins/push-notification/push-notification-driver-ox.c src/plugins/push-notification/push-notification-drivers.c src/plugins/push-notification/push-notification-drivers.h src/plugins/push-notification/push-notification-event-flagsclear.c src/plugins/push-notification/push-notification-event-flagsclear.h src/plugins/push-notification/push-notification-event-flagsset.c src/plugins/push-notification/push-notification-event-flagsset.h src/plugins/push-notification/push-notification-event-mailboxcreate.c src/plugins/push-notification/push-notification-event-mailboxcreate.h src/plugins/push-notification/push-notification-event-mailboxdelete.c src/plugins/push-notification/push-notification-event-mailboxdelete.h src/plugins/push-notification/push-notification-event-mailboxrename.c src/plugins/push-notification/push-notification-event-mailboxrename.h src/plugins/push-notification/push-notification-event-mailboxsubscribe.c src/plugins/push-notification/push-notification-event-mailboxsubscribe.h src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h src/plugins/push-notification/push-notification-event-message-common.h src/plugins/push-notification/push-notification-event-messageappend.c src/plugins/push-notification/push-notification-event-messageappend.h src/plugins/push-notification/push-notification-event-messageexpunge.c src/plugins/push-notification/push-notification-event-messageexpunge.h src/plugins/push-notification/push-notification-event-messagenew.c src/plugins/push-notification/push-notification-event-messagenew.h src/plugins/push-notification/push-notification-event-messageread.c src/plugins/push-notification/push-notification-event-messageread.h src/plugins/push-notification/push-notification-event-messagetrash.c src/plugins/push-notification/push-notification-event-messagetrash.h src/plugins/push-notification/push-notification-events-rfc5423.c src/plugins/push-notification/push-notification-events-rfc5423.h src/plugins/push-notification/push-notification-events.c src/plugins/push-notification/push-notification-events.h src/plugins/push-notification/push-notification-plugin.c src/plugins/push-notification/push-notification-plugin.h src/plugins/push-notification/push-notification-triggers.c src/plugins/push-notification/push-notification-triggers.h src/plugins/push-notification/push-notification-txn-mbox.c src/plugins/push-notification/push-notification-txn-mbox.h src/plugins/push-notification/push-notification-txn-msg.c src/plugins/push-notification/push-notification-txn-msg.h
diffstat 45 files changed, 3315 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Tue Sep 22 21:16:51 2015 +0300
+++ b/configure.ac	Tue Sep 22 22:33:41 2015 -0600
@@ -2929,6 +2929,7 @@
 src/plugins/mail-log/Makefile
 src/plugins/mailbox-alias/Makefile
 src/plugins/notify/Makefile
+src/plugins/push-notification/Makefile
 src/plugins/pop3-migration/Makefile
 src/plugins/quota/Makefile
 src/plugins/quota-clone/Makefile
--- a/dovecot-config.in.in	Tue Sep 22 21:16:51 2015 +0300
+++ b/dovecot-config.in.in	Tue Sep 22 22:33:41 2015 -0600
@@ -31,6 +31,7 @@
 LIBDOVECOT_IMAPC_INCLUDE="-I$(incdir)/src/lib-imap-client -I$(incdir)/src/lib-storage/index/imapc"
 LIBDOVECOT_FTS_INCLUDE="-I$(incdir)/src/plugins/fts"
 LIBDOVECOT_NOTIFY_INCLUDE="-I$(incdir)/src/plugins/notify"
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE="-I$(incdir)/src/plugins/push-notification"
 LIBDOVECOT_ACL_INCLUDE="-I$(incdir)/src/plugins/acl"
 
 dovecot_pkgincludedir=
--- a/src/plugins/Makefile.am	Tue Sep 22 21:16:51 2015 +0300
+++ b/src/plugins/Makefile.am	Tue Sep 22 22:33:41 2015 -0600
@@ -21,6 +21,7 @@
 	lazy-expunge \
 	listescape \
 	notify \
+	push-notification \
 	mail-filter \
 	mail-log \
 	mailbox-alias \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/Makefile.am	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,60 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-http \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/plugins/notify
+
+NOPLUGIN_LDFLAGS =
+lib20_push_notification_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = lib20_push_notification_plugin.la
+
+lib20_push_notification_plugin_la_SOURCES = \
+	push-notification-driver-dlog.c \
+	push-notification-driver-ox.c \
+	push-notification-drivers.c \
+	push-notification-event-flagsclear.c \
+	push-notification-event-flagsset.c \
+	push-notification-event-mailboxcreate.c \
+	push-notification-event-mailboxdelete.c \
+	push-notification-event-mailboxrename.c \
+	push-notification-event-mailboxsubscribe.c \
+	push-notification-event-mailboxunsubscribe.c \
+	push-notification-event-messageappend.c \
+	push-notification-event-messageexpunge.c \
+	push-notification-event-messagenew.c \
+	push-notification-event-messageread.c \
+	push-notification-event-messagetrash.c \
+	push-notification-events.c \
+	push-notification-events-rfc5423.c \
+	push-notification-plugin.c \
+	push-notification-triggers.c \
+	push-notification-txn-mbox.c \
+	push-notification-txn-msg.c
+
+headers = \
+	push-notification-drivers.h \
+	push-notification-event-flagsclear.h \
+	push-notification-event-flagsset.h \
+	push-notification-event-mailboxcreate.h \
+	push-notification-event-mailboxdelete.h \
+	push-notification-event-mailboxrename.h \
+	push-notification-event-mailboxsubscribe.h \
+	push-notification-event-mailboxunsubscribe.h \
+	push-notification-event-message-common.h \
+	push-notification-event-messageappend.h \
+	push-notification-event-messageexpunge.h \
+	push-notification-event-messagenew.h \
+	push-notification-event-messageread.h \
+	push-notification-event-messagetrash.h \
+	push-notification-events.h \
+	push-notification-events-rfc5423.h \
+	push-notification-plugin.h \
+	push-notification-triggers.h \
+	push-notification-txn-mbox.h \
+	push-notification-txn-msg.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-driver-dlog.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,116 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+
+static int
+push_notification_driver_dlog_init(struct push_notification_driver_config *config,
+                                   struct mail_user *user ATTR_UNUSED,
+                                   pool_t pool ATTR_UNUSED,
+                                   void **context ATTR_UNUSED,
+                                   const char **error_r ATTR_UNUSED)
+{
+    i_debug("Called init push_notification plugin hook.");
+
+    if (config->raw_config != NULL) {
+        i_debug("Config string for dlog push_notification driver: %s",
+                config->raw_config);
+    }
+
+    return 0;
+}
+
+static bool push_notification_driver_dlog_begin_txn
+(struct push_notification_driver_txn *dtxn)
+{
+    const struct push_notification_event *const *event;
+
+    i_debug("Called begin_txn push_notification plugin hook.");
+
+    array_foreach(&push_notification_events, event) {
+        push_notification_event_init(dtxn, (*event)->name, NULL);
+    }
+
+    return TRUE;
+}
+
+static void push_notification_driver_dlog_process_mbox
+(struct push_notification_driver_txn *dtxn ATTR_UNUSED,
+ struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_txn_event *const *event;
+
+    i_debug("Called process_mbox push_notification plugin hook.");
+
+    i_debug("Mailbox data: Mailbox [%s]", mbox->mailbox);
+
+    if (array_is_created(&mbox->eventdata)) {
+        array_foreach(&mbox->eventdata, event) {
+            if ((*event)->event->event->mbox.debug_mbox != NULL) {
+                (*event)->event->event->mbox.debug_mbox(*event);
+            }
+        }
+    }
+}
+
+static void push_notification_driver_dlog_process_msg
+(struct push_notification_driver_txn *dtxn ATTR_UNUSED,
+ struct push_notification_txn_msg *msg)
+{
+    struct push_notification_txn_event *const *event;
+
+    i_debug("Called process_msg push_notification plugin hook.");
+
+    i_debug("Message data: Mailbox [%s], UID [%u], UIDVALIDITY [%u]",
+            msg->mailbox, msg->uid, msg->uid_validity);
+
+    if (array_is_created(&msg->eventdata)) {
+        array_foreach(&msg->eventdata, event) {
+            if ((*event)->event->event->msg.debug_msg != NULL) {
+                (*event)->event->event->msg.debug_msg(*event);
+            }
+        }
+    }
+}
+
+static void push_notification_driver_dlog_end_txn
+(struct push_notification_driver_txn *dtxn ATTR_UNUSED,
+ bool success ATTR_UNUSED)
+{
+    i_debug("Called end_txn push_notification plugin hook.");
+}
+
+static void push_notification_driver_dlog_deinit
+(struct push_notification_driver_user *duser ATTR_UNUSED)
+{
+    i_debug("Called deinit push_notification plugin hook.");
+}
+
+static void push_notification_driver_dlog_cleanup(void)
+{
+    i_debug("Called cleanup push_notification plugin hook.");
+}
+
+
+/* Driver definition */
+
+extern struct push_notification_driver push_notification_driver_dlog;
+
+struct push_notification_driver push_notification_driver_dlog = {
+    .name = "dlog",
+    .v = {
+        .init = push_notification_driver_dlog_init,
+        .begin_txn = push_notification_driver_dlog_begin_txn,
+        .process_mbox = push_notification_driver_dlog_process_mbox,
+        .process_msg = push_notification_driver_dlog_process_msg,
+        .end_txn = push_notification_driver_dlog_end_txn,
+        .deinit = push_notification_driver_dlog_deinit,
+        .cleanup = push_notification_driver_dlog_cleanup
+    }
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-driver-ox.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,341 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "http-client.h"
+#include "http-url.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "json-parser.h"
+#include "mailbox-attribute.h"
+#include "mail-storage-private.h"
+#include "str.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-event-messagenew.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-msg.h"
+
+
+#define OX_LOG_LABEL "OX Push Notification: "
+
+#define OX_METADATA_KEY \
+    MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER "vendor/vendor.dovecot/http-notify"
+
+/* Default values. */
+static const char *const default_events[] = { "MessageNew", NULL };
+static const char *const default_mboxes[] = { "INBOX", NULL };
+
+
+/* This is data that is shared by all plugin users. */
+struct push_notification_driver_ox_global {
+    struct http_client *http_client;
+    int refcount;
+};
+static struct push_notification_driver_ox_global *ox_global = NULL;
+
+/* This is data specific to an OX driver. */
+struct push_notification_driver_ox_config {
+    struct http_url *http_url;
+};
+
+/* This is data specific to an OX driver transaction. */
+struct push_notification_driver_ox_txn {
+    const char *user;
+};
+
+static void
+push_notification_driver_ox_init_global(struct mail_user *user)
+{
+    struct http_client_settings http_set;
+
+    if (ox_global->http_client == NULL) {
+        memset(&http_set, 0, sizeof(http_set));
+        http_set.debug = user->mail_debug;
+
+        ox_global->http_client = http_client_init(&http_set);
+    }
+}
+
+static int
+push_notification_driver_ox_init(struct push_notification_driver_config *config,
+                                 struct mail_user *user, pool_t pool,
+                                 void **context, const char **error_r)
+{
+    struct push_notification_driver_ox_config *dconfig;
+    const char *error, *url;
+
+    /* Valid config keys: url */
+    url = hash_table_lookup(config->config, (const char *)"url");
+    if (url == NULL) {
+        *error_r = OX_LOG_LABEL "Driver requires the url parameter";
+        return -1;
+    }
+
+    dconfig = p_new(pool, struct push_notification_driver_ox_config, 1);
+
+    if (http_url_parse(url, NULL, HTTP_URL_ALLOW_USERINFO_PART, pool,
+                       &dconfig->http_url, &error) < 0) {
+        *error_r = t_strdup_printf(OX_LOG_LABEL "Failed to parse OX REST URL %s: %s",
+                                   url, error);
+        return -1;
+    }
+
+    push_notification_driver_debug(OX_LOG_LABEL, user, "Using URL %s", url);
+
+    if (ox_global == NULL) {
+        ox_global = i_new(struct push_notification_driver_ox_global, 1);
+        ox_global->refcount = 0;
+    }
+
+    ++ox_global->refcount;
+    *context = dconfig;
+
+    return 0;
+}
+
+static const char *push_notification_driver_ox_get_metadata
+(struct push_notification_driver_txn *dtxn)
+{
+    struct mail_attribute_value attr;
+    struct mailbox *inbox;
+    struct mailbox_transaction_context *mctx = NULL;
+    struct mail_namespace *ns;
+    bool success = FALSE;
+    int ret;
+
+    /* Get canonical INBOX, where private server-level metadata is stored.
+     * See imap/cmd-getmetadata.c */
+    ns = mail_namespace_find_inbox(dtxn->ptxn->muser->namespaces);
+    inbox = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY);
+    if (mailbox_open(inbox) < 0) {
+        i_error(OX_LOG_LABEL "Skipped because unable to open INBOX: %s",
+                mailbox_get_last_error(inbox, NULL));
+    } else {
+        mctx = mailbox_transaction_begin(inbox, 0);
+    }
+
+    if (mctx != NULL) {
+        ret = mailbox_attribute_get(mctx, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+                                    OX_METADATA_KEY, &attr);
+        if (ret < 0) {
+            i_error(OX_LOG_LABEL "Skipped because unable to get attribute: %s",
+                    mailbox_get_last_error(inbox, NULL));
+        } else if (ret == 0) {
+            push_notification_driver_debug(OX_LOG_LABEL, dtxn->ptxn->muser,
+                                           "Skipped because not active (/private/"OX_METADATA_KEY" METADATA not set)");
+        } else {
+            success = TRUE;
+        }
+
+        mailbox_transaction_commit(&mctx);
+    }
+
+    mailbox_free(&inbox);
+
+    return (success == TRUE) ? attr.value : NULL;
+}
+
+static bool push_notification_driver_ox_begin_txn
+(struct push_notification_driver_txn *dtxn)
+{
+    const char *const *args;
+    struct push_notification_event_messagenew_config *config;
+    const char *key, *mbox_curr, *md_value, *value;
+    bool mbox_found = FALSE;
+    struct push_notification_driver_ox_txn *txn;
+
+    md_value = push_notification_driver_ox_get_metadata(dtxn);
+    if (md_value == NULL) {
+        return FALSE;
+    }
+    struct mail_user *user = dtxn->ptxn->muser;
+
+    /* Unused keys: events, expire, folder */
+    /* TODO: To be implemented later(?) */
+    const char *const *events = default_events;
+    time_t expire = INT_MAX;
+    const char *const *mboxes = default_mboxes;
+
+    if (expire < ioloop_time) {
+        push_notification_driver_debug(OX_LOG_LABEL, user,
+                                       "Skipped due to expiration (%ld < %ld)",
+                                       (long)expire, (long)ioloop_time);
+        return FALSE;
+    }
+
+    mbox_curr = mailbox_get_vname(dtxn->ptxn->mbox);
+    for (; *mboxes != NULL; mboxes++) {
+        if (strcmp(mbox_curr, *mboxes) == 0) {
+            mbox_found = TRUE;
+            break;
+        }
+    }
+
+    if (mbox_found == FALSE) {
+        push_notification_driver_debug(OX_LOG_LABEL, user,
+                                       "Skipped because %s is not a watched mailbox",
+                                       mbox_curr);
+        return FALSE;
+    }
+
+    txn = p_new(dtxn->ptxn->pool, struct push_notification_driver_ox_txn, 1);
+
+    /* Valid keys: user */
+    args = t_strsplit_tab(md_value);
+    for (; *args != NULL; args++) {
+        key = *args;
+
+        value = strchr(key, '=');
+        if (value != NULL) {
+            key = t_strdup_until(key, value++);
+            if (strcmp(key, "user") == 0) {
+                txn->user = p_strdup(dtxn->ptxn->pool, value);
+            }
+        }
+    }
+
+    if (txn->user == NULL) {
+        i_error(OX_LOG_LABEL "No user provided in config");
+        return FALSE;
+    }
+
+    push_notification_driver_debug(OX_LOG_LABEL, user, "User (%s)", txn->user);
+
+    for (; *events != NULL; events++) {
+        if (strcmp(*events, "MessageNew") == 0) {
+            config = p_new(dtxn->ptxn->pool,
+                           struct push_notification_event_messagenew_config, 1);
+            config->flags = PUSH_NOTIFICATION_MESSAGE_HDR_FROM |
+                            PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT |
+                            PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET;
+            push_notification_event_init(dtxn, "MessageNew", config);
+            push_notification_driver_debug(OX_LOG_LABEL, user,
+                                           "Handling MessageNew event");
+        }
+    }
+
+    dtxn->context = txn;
+
+    return TRUE;
+}
+
+static void push_notification_driver_ox_http_callback
+(const struct http_response *response, struct mail_user *user)
+{
+    switch (response->status / 100) {
+    case 2:
+        // Success.
+	if (user->mail_debug) {
+            push_notification_driver_debug(OX_LOG_LABEL, user,
+                                           "Notification sent successfully: %u %s",
+                                           response->status, response->reason);
+	}
+        break;
+
+    default:
+        // Error.
+        i_error(OX_LOG_LABEL "Error when sending notification: %u %s",
+                response->status, response->reason);
+        break;
+    }
+}
+
+/* Callback needed for i_stream_add_destroy_callback() in
+ * push_notification_driver_ox_process_msg. */
+static void str_free_i(string_t *str)
+{
+    str_free(&str);
+}
+
+static void push_notification_driver_ox_process_msg
+(struct push_notification_driver_txn *dtxn,
+ struct push_notification_txn_msg *msg)
+{
+    struct push_notification_driver_ox_config *dconfig =
+        (struct push_notification_driver_ox_config *)dtxn->duser->context;
+    struct http_client_request *http_req;
+    struct push_notification_event_messagenew_data *messagenew;
+    struct istream *payload;
+    string_t *str;
+    struct push_notification_driver_ox_txn *txn =
+        (struct push_notification_driver_ox_txn *)dtxn->context;
+    struct mail_user *user = dtxn->ptxn->muser;
+
+    push_notification_driver_ox_init_global(user);
+
+    messagenew = push_notification_txn_msg_get_eventdata(msg, "MessageNew");
+
+    http_req = http_client_request_url(ox_global->http_client, "PUT",
+                                       dconfig->http_url,
+                                       push_notification_driver_ox_http_callback,
+                                       user);
+
+    http_client_request_add_header(http_req, "Content-Type",
+                                   "application/json; charset=utf-8");
+
+    str = str_new(default_pool, 256);
+    str_append(str, "{\"user\":\"");
+    json_append_escaped(str, txn->user);
+    str_append(str, "\",\"event\":\"messageNew\",\"folder\":\"");
+    json_append_escaped(str, msg->mailbox);
+    str_printfa(str, "\",\"imap-uidvalidity\":%u,\"imap-uid\":%u",
+                msg->uid_validity, msg->uid);
+    if (messagenew->from != NULL) {
+	str_append(str, ",\"from\":\"");
+	json_append_escaped(str, messagenew->from);
+    }
+    if (messagenew->subject != NULL) {
+	str_append(str, "\",\"subject\":\"");
+	json_append_escaped(str, messagenew->subject);
+    }
+    if (messagenew->snippet != NULL) {
+	str_append(str, "\",\"snippet\":\"");
+	json_append_escaped(str, messagenew->snippet);
+    }
+    str_append(str, "\"}");
+
+    push_notification_driver_debug(OX_LOG_LABEL, user,
+                                   "Sending notification: %s", str_c(str));
+
+    payload = i_stream_create_from_data(str_data(str), str_len(str));
+    i_stream_add_destroy_callback(payload, str_free_i, str);
+    http_client_request_set_payload(http_req, payload, FALSE);
+
+    http_client_request_submit(http_req);
+    i_stream_unref(&payload);
+}
+
+static void push_notification_driver_ox_deinit
+(struct push_notification_driver_user *duser ATTR_UNUSED)
+{
+    if (ox_global != NULL) {
+        i_assert(ox_global->refcount > 0);
+        --ox_global->refcount;
+    }
+}
+
+static void push_notification_driver_ox_cleanup(void)
+{
+    if ((ox_global != NULL) && (ox_global->refcount <= 0)) {
+        http_client_wait(ox_global->http_client);
+        http_client_deinit(&ox_global->http_client);
+        i_free_and_null(ox_global);
+    }
+}
+
+
+/* Driver definition */
+
+extern struct push_notification_driver push_notification_driver_ox;
+
+struct push_notification_driver push_notification_driver_ox = {
+    .name = "ox",
+    .v = {
+        .init = push_notification_driver_ox_init,
+        .begin_txn = push_notification_driver_ox_begin_txn,
+        .process_msg = push_notification_driver_ox_process_msg,
+        .deinit = push_notification_driver_ox_deinit,
+        .cleanup = push_notification_driver_ox_cleanup
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-drivers.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,189 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "mail-user.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+
+
+static ARRAY(const struct push_notification_driver *) push_notification_drivers;
+
+
+static bool
+push_notification_driver_find(const char *name, unsigned int *idx_r)
+{
+    unsigned int count, i;
+    const struct push_notification_driver *const *drivers;
+
+    drivers = array_get(&push_notification_drivers, &count);
+    for (i = 0; i < count; i++) {
+        if (strcasecmp(drivers[i]->name, name) == 0) {
+            *idx_r = i;
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static const struct push_notification_driver *
+push_notification_driver_find_class(const char *driver)
+{
+    const struct push_notification_driver *const *class_p;
+    unsigned int idx;
+
+    if (!push_notification_driver_find(driver, &idx)) {
+        return NULL;
+    }
+
+    class_p = array_idx(&push_notification_drivers, idx);
+
+    return *class_p;
+}
+
+static struct push_notification_driver_config *
+push_notification_driver_parse_config(const char *p)
+{
+    const char **args, *key, *p2, *value;
+    struct push_notification_driver_config *config;
+
+    config = t_new(struct push_notification_driver_config, 1);
+    config->raw_config = p;
+
+    hash_table_create(&config->config, unsafe_data_stack_pool, 0,
+                      str_hash, strcmp);
+
+    if (p == NULL) {
+        return config;
+    }
+
+    args = t_strsplit_spaces(p, " ");
+
+    for (; *args != NULL; args++) {
+        p2 = strchr(*args, '=');
+        if (p2 != NULL) {
+            key = t_strdup_until(*args, p2);
+            value = t_strdup(p2 + 1);
+            hash_table_insert(config->config, key, value);
+        }
+    }
+
+    return config;
+}
+
+int
+push_notification_driver_init(struct mail_user *user, const char *config_in,
+                              pool_t pool,
+                              struct push_notification_driver_user **duser_r)
+{
+    void *context = NULL;
+    const struct push_notification_driver *driver;
+    const char *driver_name, *error_r, *p;
+    struct push_notification_driver_user *duser;
+    int ret;
+
+    /* <driver>[:<driver config>] */
+    p = strchr(config_in, ':');
+    if (p == NULL) {
+        driver_name = config_in;
+    } else {
+        driver_name = t_strdup_until(config_in, p);
+    }
+
+    driver = push_notification_driver_find_class(driver_name);
+    if (driver == NULL) {
+        i_error("Unknown push notification driver: %s", driver_name);
+        return -1;
+    }
+
+    if (driver->v.init != NULL) {
+        T_BEGIN {
+            struct push_notification_driver_config *config;
+
+            config = push_notification_driver_parse_config(
+                    (p == NULL) ? p : p + 1);
+            ret = driver->v.init(config, user, pool, &context, &error_r);
+
+            hash_table_destroy(&config->config);
+        } T_END;
+
+        if (ret < 0) {
+            i_error("%s: %s", driver_name, error_r);
+            return -1;
+        }
+    }
+
+    duser = p_new(pool, struct push_notification_driver_user, 1);
+    duser->context = context;
+    duser->driver = driver;
+
+    *duser_r = duser;
+
+    return 0;
+}
+
+void push_notification_driver_cleanup_all(void)
+{
+    const struct push_notification_driver *const *driver;
+
+    /* Loop through driver list and perform global cleanup tasks. We may not
+     * have used all drivers in this plugin/worker, but the cleanup hooks are
+     * designed to ignore these unused drivers. */
+    array_foreach(&push_notification_drivers, driver) {
+        if ((*driver)->v.cleanup != NULL) {
+            (*driver)->v.cleanup();
+        }
+    }
+}
+
+void ATTR_FORMAT(3, 4)
+push_notification_driver_debug(const char *label, struct mail_user *user,
+                               const char *fmt, ...)
+{
+    va_list args;
+
+    if (user->mail_debug) T_BEGIN {
+        va_start(args, fmt);
+        i_debug("%s%s", label, t_strdup_vprintf(fmt, args));
+        va_end(args);
+    } T_END;
+}
+
+void push_notification_driver_register
+(const struct push_notification_driver *driver)
+{
+    unsigned int idx;
+
+    if (!array_is_created(&push_notification_drivers)) {
+        i_array_init(&push_notification_drivers, 4);
+    }
+
+    if (push_notification_driver_find(driver->name, &idx)) {
+        i_panic("push_notification_driver_register(%s): duplicate driver",
+                driver->name);
+    }
+
+    array_append(&push_notification_drivers, &driver, 1);
+}
+
+void push_notification_driver_unregister
+(const struct push_notification_driver *driver)
+{
+    unsigned int idx;
+
+    if (!push_notification_driver_find(driver->name, &idx)) {
+        i_panic("push_notification_driver_register(%s): unknown driver",
+                driver->name);
+    }
+
+    if (array_is_created(&push_notification_drivers)) {
+        array_delete(&push_notification_drivers, idx, 1);
+
+        if (array_is_empty(&push_notification_drivers)) {
+            array_free(&push_notification_drivers);
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-drivers.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,121 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_DRIVERS_H
+#define PUSH_NOTIFICATION_DRIVERS_H
+
+
+#include "mail-user.h"
+#include "push-notification-triggers.h"
+
+struct mail_user;
+struct push_notification_driver_config;
+struct push_notification_driver_txn;
+struct push_notification_driver_user;
+struct push_notification_txn_mbox;
+struct push_notification_txn_msg;
+
+
+HASH_TABLE_DEFINE_TYPE(push_notification_config, const char *, const char *);
+HASH_TABLE_DEFINE_TYPE(push_notification_msgs, void *,
+                       struct push_notification_txn_msg *);
+
+
+struct push_notification_driver_vfuncs {
+    /* Init driver. Config (from plugin configuration) is parsed once (no
+     * user variable substitutions). Return 0 on success, or -1 if this
+     * driver should be disabled (or on error). */
+    int (*init)(struct push_notification_driver_config *config,
+                struct mail_user *user, pool_t pool, void **context,
+                const char **error_r);
+    /* Called at the beginning of a notification transaction. Return TRUE on
+     * success, or FALSE if this driver should be ignored for this
+     * transaction. */
+    bool (*begin_txn)(struct push_notification_driver_txn *dtxn);
+    /* Called once for every mailbox processed. */
+    void (*process_mbox)(struct push_notification_driver_txn *dtxn,
+                         struct push_notification_txn_mbox *mbox);
+    /* Called once for every message processed. */
+    void (*process_msg)(struct push_notification_driver_txn *dtxn,
+                        struct push_notification_txn_msg *msg);
+    /* Called at the end of a successful notification transaction. */
+    void (*end_txn)(struct push_notification_driver_txn *dtxn, bool success);
+    /* Called when plugin is deinitialized. */
+    void (*deinit)(struct push_notification_driver_user *duser);
+    /* Called to cleanup any global resources used in plugin. */
+    void (*cleanup)(void);
+};
+
+struct push_notification_driver {
+    const char *name;
+    struct push_notification_driver_vfuncs v;
+};
+
+struct push_notification_driver_config {
+    HASH_TABLE_TYPE(push_notification_config) config;
+    const char *raw_config;
+};
+
+struct push_notification_driver_user {
+    const struct push_notification_driver *driver;
+    void *context;
+};
+
+struct push_notification_driver_txn {
+    const struct push_notification_driver_user *duser;
+    struct push_notification_txn *ptxn;
+
+    /* Transaction context. */
+    void *context;
+};
+
+struct push_notification_user {
+    union mail_user_module_context module_ctx;
+    ARRAY(struct push_notification_driver_user *) drivers;
+    pool_t pool;
+};
+
+struct push_notification_trigger_ctx {
+    const char *name;
+    void *context;
+};
+
+struct push_notification_txn {
+    pool_t pool;
+
+    struct mailbox *mbox;
+    struct mail_user *muser;
+    struct push_notification_user *puser;
+
+    enum push_notification_event_trigger trigger;
+    struct push_notification_trigger_ctx *trigger_ctx;
+    ARRAY(struct push_notification_driver_txn *) drivers;
+    ARRAY(struct push_notification_event_config *) events;
+
+    /* Used with mailbox events. */
+    struct push_notification_txn_mbox *mbox_txn;
+
+    /* Used with mailbox events. */
+    HASH_TABLE_TYPE(push_notification_msgs) messages;
+
+    /* Private (used with message events). */
+    struct mailbox_transaction_context *t;
+};
+
+
+int
+push_notification_driver_init(struct mail_user *user, const char *config_in,
+                              pool_t pool,
+                              struct push_notification_driver_user **duser_r);
+void push_notification_driver_cleanup_all(void);
+
+void ATTR_FORMAT(3, 4)
+push_notification_driver_debug(const char *label, struct mail_user *user,
+                               const char *fmt, ...);
+
+void push_notification_driver_register
+(const struct push_notification_driver *driver);
+void push_notification_driver_unregister
+(const struct push_notification_driver *driver);
+
+
+#endif /* PUSH_NOTIFICATION_DRIVERS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-flagsclear.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,179 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-flagsclear.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "FlagsClear"
+
+static struct push_notification_event_flagsclear_config default_config;
+
+
+static void *push_notification_event_flagsclear_default_config(void)
+{
+    memset(&default_config, 0, sizeof(default_config));
+
+    return &default_config;
+}
+
+static void push_notification_event_flagsclear_debug_msg
+(struct push_notification_txn_event *event)
+{
+    struct push_notification_event_flagsclear_data *data = event->data;
+    const char *const *keyword;
+
+    if (data->flags_clear & MAIL_ANSWERED) {
+        i_debug("%s: Answered flag cleared", EVENT_NAME);
+    }
+    if (data->flags_clear & MAIL_FLAGGED) {
+        i_debug("%s: Flagged flag cleared", EVENT_NAME);
+    }
+    if (data->flags_clear & MAIL_DELETED) {
+        i_debug("%s: Deleted flag cleared", EVENT_NAME);
+    }
+    if (data->flags_clear & MAIL_SEEN) {
+        i_debug("%s: Seen flag cleared", EVENT_NAME);
+    }
+    if (data->flags_clear & MAIL_DRAFT) {
+        i_debug("%s: Draft flag cleared", EVENT_NAME);
+    }
+
+    array_foreach(&data->keywords_clear, keyword) {
+        i_debug("%s: Keyword clear [%s]", EVENT_NAME, *keyword);
+    }
+}
+
+static struct push_notification_event_flagsclear_data *
+push_notification_event_flagsclear_get_data(struct push_notification_txn *ptxn,
+                                            struct push_notification_txn_msg *msg,
+                                            struct push_notification_event_config *ec)
+{
+    struct push_notification_event_flagsclear_config *config =
+        (struct push_notification_event_flagsclear_config *)ec->config;
+    struct push_notification_event_flagsclear_data *data;
+
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if (data == NULL) {
+        data = p_new(ptxn->pool,
+                     struct push_notification_event_flagsclear_data, 1);
+        data->flags_clear = 0;
+        data->flags_old = 0;
+        p_array_init(&data->keywords_clear, ptxn->pool, 4);
+        if (config->store_old == TRUE) {
+            p_array_init(&data->keywords_old, ptxn->pool, 4);
+        }
+
+        push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+    }
+
+    return data;
+}
+
+static void push_notification_event_flagsclear_flags_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg,
+    struct mail *mail,
+    enum mail_flags old_flags)
+{
+    struct push_notification_event_flagsclear_config *config =
+        (struct push_notification_event_flagsclear_config *)ec->config;
+    struct push_notification_event_flagsclear_data *data;
+    enum mail_flags flag_check_always[] = {
+        MAIL_ANSWERED,
+        MAIL_DELETED,
+        MAIL_DRAFT,
+        MAIL_FLAGGED,
+        MAIL_SEEN
+    };
+    enum mail_flags flags;
+    unsigned int i;
+
+    data = push_notification_event_flagsclear_get_data(ptxn, msg, ec);
+    flags = mail_get_flags(mail);
+
+    for (i = 0; i < N_ELEMENTS(flag_check_always); i++) {
+        if (!(flags & flag_check_always[i]) &&
+            (old_flags & flag_check_always[i])) {
+            data->flags_clear |= flag_check_always[i];
+        }
+    }
+
+    if (config->store_old == TRUE) {
+        data->flags_old = old_flags;
+    }
+}
+
+static void push_notification_event_flagsclear_keywords_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg,
+    struct mail *mail,
+    const char *const *old_keywords)
+{
+    struct push_notification_event_flagsclear_config *config =
+        (struct push_notification_event_flagsclear_config *)ec->config;
+    struct push_notification_event_flagsclear_data *data;
+    const char *const *keywords, *const *kp, *ok;
+
+    data = push_notification_event_flagsclear_get_data(ptxn, msg, ec);
+    keywords = mail_get_keywords(mail);
+
+    for (; *old_keywords != NULL; old_keywords++) {
+        for (kp = keywords; *kp != NULL; kp++) {
+            if (strcmp(*old_keywords, *kp) == 0) {
+                break;
+            }
+        }
+
+        if (*kp == NULL) {
+            ok = p_strdup(ptxn->pool, *old_keywords);
+            array_append(&data->keywords_clear, &ok, 1);
+        }
+
+        if (config->store_old == TRUE) {
+            ok = p_strdup(ptxn->pool, *old_keywords);
+            array_append(&data->keywords_old, &ok, 1);
+        }
+    }
+}
+
+static void push_notification_event_flagsclear_free_msg(
+    struct push_notification_txn_event *event)
+{
+    struct push_notification_event_flagsclear_data *data = event->data;
+
+    if (array_is_created(&data->keywords_clear)) {
+        array_free(&data->keywords_clear);
+    }
+    if (array_is_created(&data->keywords_old)) {
+        array_free(&data->keywords_old);
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_flagsclear;
+
+struct push_notification_event push_notification_event_flagsclear = {
+    .name = EVENT_NAME,
+    .init = {
+        .default_config = push_notification_event_flagsclear_default_config
+    },
+    .msg = {
+        .debug_msg = push_notification_event_flagsclear_debug_msg,
+        .free_msg = push_notification_event_flagsclear_free_msg
+    },
+    .msg_triggers = {
+        .flagchange = push_notification_event_flagsclear_flags_event,
+        .keywordchange = push_notification_event_flagsclear_keywords_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-flagsclear.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,25 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H
+#define PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H
+
+
+#include "mail-types.h"
+
+
+struct push_notification_event_flagsclear_config {
+    /* Store the old flags/keywords? */
+    bool store_old;
+};
+
+struct push_notification_event_flagsclear_data {
+    enum mail_flags flags_clear;
+    ARRAY_TYPE(keywords) keywords_clear;
+
+    enum mail_flags flags_old;
+    ARRAY_TYPE(keywords) keywords_old;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_FLAGSCLEAR_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-flagsset.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,174 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-flagsset.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "FlagsSet"
+
+static struct push_notification_event_flagsset_config default_config;
+
+
+static void *push_notification_event_flagsset_default_config(void)
+{
+    memset(&default_config, 0, sizeof(default_config));
+
+    return &default_config;
+}
+
+static void push_notification_event_flagsset_debug_msg
+(struct push_notification_txn_event *event)
+{
+    struct push_notification_event_flagsset_data *data = event->data;
+    const char *const *keyword;
+
+    if (data->flags_set & MAIL_ANSWERED) {
+        i_debug("%s: Answered flag set", EVENT_NAME);
+    }
+    if (data->flags_set & MAIL_FLAGGED) {
+        i_debug("%s: Flagged flag set", EVENT_NAME);
+    }
+    if (data->flags_set & MAIL_DELETED) {
+        i_debug("%s: Deleted flag set", EVENT_NAME);
+    }
+    if (data->flags_set & MAIL_SEEN) {
+        i_debug("%s: Seen flag set", EVENT_NAME);
+    }
+    if (data->flags_set & MAIL_DRAFT) {
+        i_debug("%s: Draft flag set", EVENT_NAME);
+    }
+
+    array_foreach(&data->keywords_set, keyword) {
+        i_debug("%s: Keyword set [%s]", EVENT_NAME, *keyword);
+    }
+}
+
+static struct push_notification_event_flagsset_data *
+push_notification_event_flagsset_get_data(struct push_notification_txn *ptxn,
+                                          struct push_notification_txn_msg *msg,
+                                          struct push_notification_event_config *ec)
+{
+    struct push_notification_event_flagsset_data *data;
+
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if (data == NULL) {
+        data = p_new(ptxn->pool,
+                     struct push_notification_event_flagsset_data, 1);
+        data->flags_set = 0;
+        p_array_init(&data->keywords_set, ptxn->pool, 4);
+
+        push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+    }
+
+    return data;
+}
+
+static void push_notification_event_flagsset_flags_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg,
+    struct mail *mail,
+    enum mail_flags old_flags)
+{
+    struct push_notification_event_flagsset_config *config =
+        (struct push_notification_event_flagsset_config *)ec->config;
+    struct push_notification_event_flagsset_data *data;
+    enum mail_flags flag_check_always[] = {
+        MAIL_ANSWERED,
+        MAIL_DRAFT,
+        MAIL_FLAGGED
+    };
+    enum mail_flags flags, flags_set = 0;
+    unsigned int i;
+
+    flags = mail_get_flags(mail);
+
+    for (i = 0; i < N_ELEMENTS(flag_check_always); i++) {
+        if ((flags & flag_check_always[i]) &&
+            !(old_flags & flag_check_always[i])) {
+            flags_set |= flag_check_always[i];
+        }
+    }
+
+    if (!config->hide_deleted &&
+        (flags & MAIL_DELETED) &&
+        !(old_flags & MAIL_DELETED)) {
+        flags_set |= MAIL_DELETED;
+    }
+
+    if (!config->hide_seen &&
+        (flags & MAIL_SEEN) &&
+        !(old_flags & MAIL_SEEN)) {
+        flags_set |= MAIL_SEEN;
+    }
+
+    /* Only create data element if at least one flag was set. */
+    if (flags_set) {
+        data = push_notification_event_flagsset_get_data(ptxn, msg, ec);
+        data->flags_set |= flags_set;
+    }
+}
+
+static void push_notification_event_flagsset_keywords_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg,
+    struct mail *mail,
+    const char *const *old_keywords)
+{
+    struct push_notification_event_flagsset_data *data;
+    const char *k, *const *keywords, *const *op;
+
+    data = push_notification_event_flagsset_get_data(ptxn, msg, ec);
+    keywords = mail_get_keywords(mail);
+
+    for (; *keywords != NULL; keywords++) {
+        for (op = old_keywords; *op != NULL; op++) {
+            if (strcmp(*keywords, *op) == 0) {
+                break;
+            }
+        }
+
+        if (*op == NULL) {
+            k = p_strdup(ptxn->pool, *keywords);
+            array_append(&data->keywords_set, &k, 1);
+        }
+    }
+}
+
+static void push_notification_event_flagsset_free_msg(
+    struct push_notification_txn_event *event)
+{
+    struct push_notification_event_flagsset_data *data = event->data;
+
+    if (array_is_created(&data->keywords_set)) {
+       array_free(&data->keywords_set);
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_flagsset;
+
+struct push_notification_event push_notification_event_flagsset = {
+    .name = EVENT_NAME,
+    .init = {
+        .default_config = push_notification_event_flagsset_default_config
+    },
+    .msg = {
+        .debug_msg = push_notification_event_flagsset_debug_msg,
+        .free_msg = push_notification_event_flagsset_free_msg
+    },
+    .msg_triggers = {
+        .flagchange = push_notification_event_flagsset_flags_event,
+        .keywordchange = push_notification_event_flagsset_keywords_event
+    }
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-flagsset.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,26 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_FLAGSSET_H
+#define PUSH_NOTIFICATION_EVENT_FLAGSSET_H
+
+
+#include "mail-types.h"
+
+
+struct push_notification_event_flagsset_config {
+    /* RFC 5423[4.2] - allow configuration whether FlagsSet event returns
+     * Deleted and/or Seen flags, since these flags are also settable
+     * via MessageRead/MessageTrash events. By default, include them
+     * here. */
+    bool hide_deleted;
+    bool hide_seen;
+};
+
+struct push_notification_event_flagsset_data {
+    enum mail_flags flags_set;
+    ARRAY_TYPE(keywords) keywords_set;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_FLAGSSET_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxcreate.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,51 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxcreate.h"
+#include "push-notification-txn-mbox.h"
+
+
+#define EVENT_NAME "MailboxCreate"
+
+
+static void push_notification_event_mailboxcreate_debug_mbox
+(struct push_notification_txn_event *event ATTR_UNUSED)
+{
+    i_debug("%s: Mailbox was created", EVENT_NAME);
+}
+
+static void push_notification_event_mailboxcreate_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_mailboxcreate_data *data;
+    struct mailbox_status status;
+
+    mailbox_get_status(ptxn->mbox, STATUS_UIDVALIDITY, &status);
+
+    data = p_new(ptxn->pool,
+                 struct push_notification_event_mailboxcreate_data, 1);
+    data->uid_validity = status.uidvalidity;
+
+    push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxcreate;
+
+struct push_notification_event push_notification_event_mailboxcreate = {
+    .name = EVENT_NAME,
+    .mbox = {
+        .debug_mbox = push_notification_event_mailboxcreate_debug_mbox
+    },
+    .mbox_triggers = {
+        .create = push_notification_event_mailboxcreate_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxcreate.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,14 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H
+
+
+struct push_notification_event_mailboxcreate_data {
+    /* RFC 5423 [4.4]: UIDVALIDITY required for create event. */
+    uint32_t uid_validity;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MAILBOXCREATE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxdelete.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,47 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxdelete.h"
+#include "push-notification-txn-mbox.h"
+
+
+#define EVENT_NAME "MailboxDelete"
+
+
+static void push_notification_event_mailboxdelete_debug_mbox
+(struct push_notification_txn_event *event ATTR_UNUSED)
+{
+    i_debug("%s: Mailbox was deleted", EVENT_NAME);
+}
+
+static void push_notification_event_mailboxdelete_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_mailboxdelete_data *data;
+
+    data = p_new(ptxn->pool,
+                 struct push_notification_event_mailboxdelete_data, 1);
+    data->deleted = TRUE;
+
+    push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxdelete;
+
+struct push_notification_event push_notification_event_mailboxdelete = {
+    .name = EVENT_NAME,
+    .mbox = {
+        .debug_mbox = push_notification_event_mailboxdelete_debug_mbox
+    },
+    .mbox_triggers = {
+        .delete = push_notification_event_mailboxdelete_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxdelete.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,14 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H
+
+
+struct push_notification_event_mailboxdelete_data {
+    /* Can only be true. */
+    bool deleted;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MAILBOXDELETE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxrename.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,52 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxrename.h"
+#include "push-notification-txn-mbox.h"
+
+
+#define EVENT_NAME "MailboxRename"
+
+
+static void push_notification_event_mailboxrename_debug_mbox
+(struct push_notification_txn_event *event)
+{
+    struct push_notification_event_mailboxrename_data *data = event->data;
+
+    i_debug("%s: Mailbox was renamed (old name: %s)",
+            EVENT_NAME, data->old_mbox);
+}
+
+static void push_notification_event_mailboxrename_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_mbox *mbox,
+    struct mailbox *old)
+{
+    struct push_notification_event_mailboxrename_data *data;
+
+    data = p_new(ptxn->pool,
+                 struct push_notification_event_mailboxrename_data, 1);
+    data->old_mbox = mailbox_get_vname(old);
+
+    push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxrename;
+
+struct push_notification_event push_notification_event_mailboxrename = {
+    .name = EVENT_NAME,
+    .mbox = {
+        .debug_mbox = push_notification_event_mailboxrename_debug_mbox
+    },
+    .mbox_triggers = {
+        .rename = push_notification_event_mailboxrename_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxrename.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,13 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H
+
+
+struct push_notification_event_mailboxrename_data {
+    const char *old_mbox;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MAILBOXRENAME_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,48 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxsubscribe.h"
+#include "push-notification-txn-mbox.h"
+
+
+#define EVENT_NAME "MailboxSubscribe"
+
+
+static void push_notification_event_mailboxsubscribe_debug_mbox
+(struct push_notification_txn_event *event ATTR_UNUSED)
+{
+    i_debug("%s: Mailbox was subscribed to", EVENT_NAME);
+}
+
+static void push_notification_event_mailboxsubscribe_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_mailboxsubscribe_data *data;
+
+    data = p_new(ptxn->pool,
+                 struct push_notification_event_mailboxsubscribe_data, 1);
+    data->subscribe = TRUE;
+
+    push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxsubscribe;
+
+struct push_notification_event push_notification_event_mailboxsubscribe = {
+    .name = EVENT_NAME,
+    .mbox = {
+        .debug_mbox = push_notification_event_mailboxsubscribe_debug_mbox
+    },
+    .mbox_triggers = {
+        .subscribe = push_notification_event_mailboxsubscribe_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxsubscribe.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,14 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H
+
+
+struct push_notification_event_mailboxsubscribe_data {
+    /* Can only be true. */
+    bool subscribe;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MAILBOXSUBSCRIBE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,48 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-mailboxunsubscribe.h"
+#include "push-notification-txn-mbox.h"
+
+
+#define EVENT_NAME "MailboxUnsubscribe"
+
+
+static void push_notification_event_mailboxunsubscribe_debug_mbox
+(struct push_notification_txn_event *event ATTR_UNUSED)
+{
+    i_debug("%s: Mailbox was subscribed to", EVENT_NAME);
+}
+
+static void push_notification_event_mailboxunsubscribe_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_mailboxunsubscribe_data *data;
+
+    data = p_new(ptxn->pool,
+                 struct push_notification_event_mailboxunsubscribe_data, 1);
+    data->subscribe = TRUE;
+
+    push_notification_txn_mbox_set_eventdata(ptxn, mbox, ec, data);
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_mailboxunsubscribe;
+
+struct push_notification_event push_notification_event_mailboxunsubscribe = {
+    .name = EVENT_NAME,
+    .mbox = {
+        .debug_mbox = push_notification_event_mailboxunsubscribe_debug_mbox
+    },
+    .mbox_triggers = {
+        .unsubscribe = push_notification_event_mailboxunsubscribe_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-mailboxunsubscribe.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,14 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H
+#define PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H
+
+
+struct push_notification_event_mailboxunsubscribe_data {
+    /* Can only be false. */
+    bool subscribe;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MAILBOXUNSUBSCRIBE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-message-common.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,22 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H
+
+
+enum push_notification_event_message_flags {
+    /* Header: From */
+    PUSH_NOTIFICATION_MESSAGE_HDR_FROM = 0x01,
+    /* Header: To */
+    PUSH_NOTIFICATION_MESSAGE_HDR_TO = 0x02,
+    /* Header: Subject */
+    PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT = 0x04,
+    /* Header: Date */
+    PUSH_NOTIFICATION_MESSAGE_HDR_DATE = 0x08,
+    /* Body: Snippet */
+    PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET = 0x10
+};
+
+
+#endif	/* PUSH_NOTIFICATION_EVENT_MESSAGE_COMMON_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messageappend.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,115 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-message-common.h"
+#include "push-notification-event-messageappend.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "MessageAppend"
+
+static struct push_notification_event_messageappend_config default_config;
+
+
+static void *push_notification_event_messageappend_default_config(void)
+{
+    memset(&default_config, 0, sizeof(default_config));
+
+    return &default_config;
+}
+
+static void push_notification_event_messageappend_debug_msg
+(struct push_notification_txn_event *event)
+{
+    struct push_notification_event_messageappend_data *data = event->data;
+
+    if (data->from != NULL) {
+        i_debug("%s: From [%s]", EVENT_NAME, data->from);
+    }
+
+    if (data->snippet != NULL) {
+        i_debug("%s: Snippet [%s]", EVENT_NAME, data->snippet);
+    }
+
+    if (data->subject != NULL) {
+        i_debug("%s: Subject [%s]", EVENT_NAME, data->subject);
+    }
+
+    if (data->to != NULL) {
+        i_debug("%s: To [%s]", EVENT_NAME, data->to);
+    }
+}
+
+static void
+push_notification_event_messageappend_event(struct push_notification_txn *ptxn,
+                                            struct push_notification_event_config *ec,
+                                            struct push_notification_txn_msg *msg,
+                                            struct mail *mail)
+{
+    struct push_notification_event_messageappend_config *config =
+        (struct push_notification_event_messageappend_config *)ec->config;
+    struct push_notification_event_messageappend_data *data;
+    const char *value;
+
+    if (!config->flags) {
+        return;
+    }
+
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if (data == NULL) {
+        data = p_new(ptxn->pool,
+                     struct push_notification_event_messageappend_data, 1);
+        push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+    }
+
+    if ((data->to == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_TO) &&
+        (mail_get_first_header(mail, "To", &value) >= 0)) {
+        data->to = p_strdup(ptxn->pool, value);
+    }
+
+    if ((data->from == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_FROM) &&
+        (mail_get_first_header(mail, "From", &value) >= 0)) {
+        data->from = p_strdup(ptxn->pool, value);
+    }
+
+    if ((data->subject == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT) &&
+        (mail_get_first_header(mail, "Subject", &value) >= 0)) {
+        data->subject = p_strdup(ptxn->pool, value);
+    }
+
+    if ((data->snippet == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET) &&
+        (mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &value) >= 0)) {
+        /* [0] contains the snippet algorithm, skip over it */
+        i_assert(value[0] != '\0');
+        data->snippet = p_strdup(ptxn->pool, value + 1);
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messageappend;
+
+struct push_notification_event push_notification_event_messageappend = {
+    .name = EVENT_NAME,
+    .init = {
+        .default_config = push_notification_event_messageappend_default_config
+    },
+    .msg = {
+        .debug_msg = push_notification_event_messageappend_debug_msg
+    },
+    .msg_triggers = {
+        .append = push_notification_event_messageappend_event
+    }
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messageappend.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,20 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H
+
+
+struct push_notification_event_messageappend_config {
+    enum push_notification_event_message_flags flags;
+};
+
+struct push_notification_event_messageappend_data {
+    const char *from;
+    const char *to;
+    const char *subject;
+    const char *snippet;
+};
+
+
+#endif	/* PUSH_NOTIFICATION_EVENT_MESSAGEAPPEND_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messageexpunge.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,55 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-event-messageexpunge.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "MessageExpunge"
+
+
+static void push_notification_event_messageexpunge_debug_msg
+(struct push_notification_txn_event *event)
+{
+    struct push_notification_event_messageexpunge_data *data = event->data;
+
+    if (data != NULL) {
+        i_debug("%s: Message was expunged", EVENT_NAME);
+    }
+}
+
+static void push_notification_event_messageexpunge_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg)
+{
+    struct push_notification_event_messageexpunge_data *data;
+
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if (data == NULL) {
+        data = p_new(ptxn->pool,
+                     struct push_notification_event_messageexpunge_data, 1);
+        data->expunge = TRUE;
+        push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messageexpunge;
+
+struct push_notification_event push_notification_event_messageexpunge = {
+    .name = EVENT_NAME,
+    .msg = {
+        .debug_msg = push_notification_event_messageexpunge_debug_msg
+    },
+    .msg_triggers = {
+        .expunge = push_notification_event_messageexpunge_event
+    }
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messageexpunge.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,14 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H
+
+
+struct push_notification_event_messageexpunge_data {
+    /* Can only be true. */
+    bool expunge;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MESSAGEEXPUNGE_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messagenew.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,136 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "iso8601-date.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include <time.h>
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-message-common.h"
+#include "push-notification-event-messagenew.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "MessageNew"
+
+static struct push_notification_event_messagenew_config default_config;
+
+
+static void *push_notification_event_messagenew_default_config(void)
+{
+    memset(&default_config, 0, sizeof(default_config));
+
+    return &default_config;
+}
+
+static void push_notification_event_messagenew_debug_msg
+(struct push_notification_txn_event *event)
+{
+    struct push_notification_event_messagenew_data *data = event->data;
+    struct tm *tm;
+
+    if (data->date != -1) {
+        tm = gmtime(&data->date);
+        i_debug("%s: Date [%s]", EVENT_NAME,
+                iso8601_date_create_tm(tm, data->date_tz));
+    }
+
+    if (data->from != NULL) {
+        i_debug("%s: From [%s]", EVENT_NAME, data->from);
+    }
+
+    if (data->snippet != NULL) {
+        i_debug("%s: Snippet [%s]", EVENT_NAME, data->snippet);
+    }
+
+    if (data->subject != NULL) {
+        i_debug("%s: Subject [%s]", EVENT_NAME, data->subject);
+    }
+
+    if (data->to != NULL) {
+        i_debug("%s: To [%s]", EVENT_NAME, data->to);
+    }
+}
+
+static void
+push_notification_event_messagenew_event(struct push_notification_txn *ptxn,
+                                         struct push_notification_event_config *ec,
+                                         struct push_notification_txn_msg *msg,
+                                         struct mail *mail)
+{
+    struct push_notification_event_messagenew_config *config =
+        (struct push_notification_event_messagenew_config *)ec->config;
+    struct push_notification_event_messagenew_data *data;
+    time_t date;
+    int tz;
+    const char *value;
+
+    if (!config->flags) {
+        return;
+    }
+
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if (data == NULL) {
+        data = p_new(ptxn->pool,
+                     struct push_notification_event_messagenew_data, 1);
+        data->date = -1;
+
+        push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+    }
+
+    if ((data->to == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_TO) &&
+        (mail_get_first_header(mail, "To", &value) >= 0)) {
+        data->to = p_strdup(ptxn->pool, value);
+    }
+
+    if ((data->from == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_FROM) &&
+        (mail_get_first_header(mail, "From", &value) >= 0)) {
+        data->from = p_strdup(ptxn->pool, value);
+    }
+
+    if ((data->subject == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT) &&
+        (mail_get_first_header(mail, "Subject", &value) >= 0)) {
+        data->subject = p_strdup(ptxn->pool, value);
+    }
+
+    if ((data->date == -1) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_HDR_DATE) &&
+        (mail_get_date(mail, &date, &tz) >= 0)) {
+        data->date = date;
+        data->date_tz = tz;
+    }
+
+    if ((data->snippet == NULL) &&
+        (config->flags & PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET) &&
+        (mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &value) >= 0)) {
+        /* [0] contains the snippet algorithm, skip over it */
+        i_assert(value[0] != '\0');
+        data->snippet = p_strdup(ptxn->pool, value + 1);
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messagenew;
+
+struct push_notification_event push_notification_event_messagenew = {
+    .name = EVENT_NAME,
+    .init = {
+        .default_config = push_notification_event_messagenew_default_config
+    },
+    .msg = {
+        .debug_msg = push_notification_event_messagenew_debug_msg
+    },
+    .msg_triggers = {
+        .save = push_notification_event_messagenew_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messagenew.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,30 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGENEW_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGENEW_H
+
+
+#include "push-notification-event-message-common.h"
+
+
+struct push_notification_event_messagenew_config {
+    enum push_notification_event_message_flags flags;
+};
+
+struct push_notification_event_messagenew_data {
+    /* PUSH_NOTIFICATION_MESSAGE_HDR_FROM */
+    const char *from;
+    /* PUSH_NOTIFICATION_MESSAGE_HDR_TO */
+    const char *to;
+    /* PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT */
+    const char *subject;
+    /* PUSH_NOTIFICATION_MESSAGE_HDR_DATE */
+    time_t date;
+    int date_tz;
+    /* PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET */
+    const char *snippet;
+};
+
+
+#endif	/* PUSH_NOTIFICATION_EVENT_MESSAGENEW_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messageread.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,58 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-messageread.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "MessageRead"
+
+
+static void push_notification_event_messageread_debug_msg
+(struct push_notification_txn_event *event ATTR_UNUSED)
+{
+    i_debug("%s: Message was flagged as seen", EVENT_NAME);
+}
+
+static void push_notification_event_messageread_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg,
+    struct mail *mail,
+    enum mail_flags old_flags)
+{
+    struct push_notification_event_messageread_data *data;
+    enum mail_flags flags;
+
+    /* If data struct exists, that means the read flag was changed. */
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if ((data == NULL) && !(old_flags & MAIL_SEEN)) {
+        flags = mail_get_flags(mail);
+        if (flags & MAIL_SEEN) {
+            data = p_new(ptxn->pool,
+                         struct push_notification_event_messageread_data, 1);
+            data->read = TRUE;
+            push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+        }
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messageread;
+
+struct push_notification_event push_notification_event_messageread = {
+    .name = EVENT_NAME,
+    .msg = {
+        .debug_msg = push_notification_event_messageread_debug_msg
+    },
+    .msg_triggers = {
+        .flagchange = push_notification_event_messageread_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messageread.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,13 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H
+
+
+struct push_notification_event_messageread_data {
+    /* Can only be true. */
+    bool read;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MESSAGEREAD_H */
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messagetrash.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,58 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-types.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-event-messagetrash.h"
+#include "push-notification-txn-msg.h"
+
+
+#define EVENT_NAME "MessageTrash"
+
+
+static void push_notification_event_messagetrash_debug_msg
+(struct push_notification_txn_event *event ATTR_UNUSED)
+{
+    i_debug("%s: Message was marked as deleted", EVENT_NAME);
+}
+
+static void push_notification_event_messagetrash_event(
+    struct push_notification_txn *ptxn,
+    struct push_notification_event_config *ec,
+    struct push_notification_txn_msg *msg,
+    struct mail *mail,
+    enum mail_flags old_flags)
+{
+    struct push_notification_event_messagetrash_data *data;
+    enum mail_flags flags;
+
+    /* If data struct exists, that means the deleted flag was changed. */
+    data = push_notification_txn_msg_get_eventdata(msg, EVENT_NAME);
+    if ((data == NULL) && !(old_flags & MAIL_DELETED)) {
+        flags = mail_get_flags(mail);
+        if (flags & MAIL_DELETED) {
+            data = p_new(ptxn->pool,
+                         struct push_notification_event_messagetrash_data, 1);
+            data->trash = TRUE;
+            push_notification_txn_msg_set_eventdata(ptxn, msg, ec, data);
+        }
+    }
+}
+
+
+/* Event definition */
+
+extern struct push_notification_event push_notification_event_messagetrash;
+
+struct push_notification_event push_notification_event_messagetrash = {
+    .name = EVENT_NAME,
+    .msg = {
+        .debug_msg = push_notification_event_messagetrash_debug_msg
+    },
+    .msg_triggers = {
+        .flagchange = push_notification_event_messagetrash_event
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-event-messagetrash.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,14 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H
+#define PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H
+
+
+struct push_notification_event_messagetrash_data {
+    /* Can only be true. */
+    bool trash;
+};
+
+
+#endif /* PUSH_NOTIFICATION_EVENT_MESSAGETRASH_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-events-rfc5423.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,46 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#include "push-notification-events.h"
+#include "push-notification-events-rfc5423.h"
+
+
+/* These are the RFC 5423 Mail Store Events currently handled within the core
+ * push-notification code.
+ *
+ * @todo: These events are not currently handled:
+ *   - Login
+ *   - Logout
+ *   - QuotaExceed
+ *   - Quota Within
+ */
+extern struct push_notification_event push_notification_event_flagsclear;
+extern struct push_notification_event push_notification_event_flagsset;
+extern struct push_notification_event push_notification_event_mailboxcreate;
+extern struct push_notification_event push_notification_event_mailboxdelete;
+extern struct push_notification_event push_notification_event_mailboxrename;
+extern struct push_notification_event push_notification_event_mailboxsubscribe;
+extern struct push_notification_event push_notification_event_mailboxunsubscribe;
+extern struct push_notification_event push_notification_event_messageappend;
+extern struct push_notification_event push_notification_event_messageexpunge;
+extern struct push_notification_event push_notification_event_messagenew;
+extern struct push_notification_event push_notification_event_messageread;
+extern struct push_notification_event push_notification_event_messagetrash;
+
+
+void push_notification_event_register_rfc5423_events(void)
+{
+    push_notification_event_register(&push_notification_event_flagsclear);
+    push_notification_event_register(&push_notification_event_flagsset);
+    push_notification_event_register(&push_notification_event_mailboxcreate);
+    push_notification_event_register(&push_notification_event_mailboxdelete);
+    push_notification_event_register(&push_notification_event_mailboxrename);
+    push_notification_event_register(&push_notification_event_mailboxsubscribe);
+    push_notification_event_register(&push_notification_event_mailboxunsubscribe);
+    push_notification_event_register(&push_notification_event_messageappend);
+    push_notification_event_register(&push_notification_event_messageexpunge);
+    push_notification_event_register(&push_notification_event_messagenew);
+    push_notification_event_register(&push_notification_event_messageread);
+    push_notification_event_register(&push_notification_event_messagetrash);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-events-rfc5423.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,11 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENTS_RFC5423_H
+#define PUSH_NOTIFICATION_EVENTS_RFC5423_H
+
+
+void push_notification_event_register_rfc5423_events(void);
+
+
+#endif	/* PUSH_NOTIFICATION_EVENTS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-events.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,105 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+
+
+ARRAY_TYPE(push_notification_event) push_notification_events;
+
+
+static bool
+push_notification_event_find(const char *name, unsigned int *idx_r)
+{
+    unsigned int count, i;
+    const struct push_notification_event *const *events;
+
+    events = array_get(&push_notification_events, &count);
+    for (i = 0; i < count; i++) {
+        if (strcasecmp(events[i]->name, name) == 0) {
+            *idx_r = i;
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static const struct push_notification_event *
+push_notification_event_find_class(const char *driver)
+{
+    const struct push_notification_event *const *class_p;
+    unsigned int idx;
+
+    if (!push_notification_event_find(driver, &idx)) {
+        return NULL;
+    }
+
+    class_p = array_idx(&push_notification_events, idx);
+
+    return *class_p;
+}
+
+void
+push_notification_event_init(struct push_notification_driver_txn *dtxn,
+                             const char *event_name, void *config)
+{
+    const struct push_notification_event *event;
+    struct push_notification_event_config *ec;
+
+    if (!array_is_created(&dtxn->ptxn->events)) {
+        p_array_init(&dtxn->ptxn->events, dtxn->ptxn->pool, 4);
+    }
+
+    event = push_notification_event_find_class(event_name);
+    if (event != NULL) {
+        if ((config == NULL) &&
+            (event->init.default_config != NULL)) {
+            config = event->init.default_config();
+        }
+
+        ec = p_new(dtxn->ptxn->pool, struct push_notification_event_config, 1);
+        ec->config = config;
+        ec->event = event;
+
+        array_append(&dtxn->ptxn->events, &ec, 1);
+    }
+}
+
+void push_notification_event_register
+(const struct push_notification_event *event)
+{
+    unsigned int idx;
+
+    if (!array_is_created(&push_notification_events)) {
+        i_array_init(&push_notification_events, 16);
+    }
+
+    if (push_notification_event_find(event->name, &idx)) {
+        i_panic("push_notification_event_register(%s): duplicate event",
+                event->name);
+    }
+
+    array_append(&push_notification_events, &event, 1);
+}
+
+void push_notification_event_unregister
+(const struct push_notification_event *event)
+{
+    unsigned int idx;
+
+    if (!push_notification_event_find(event->name, &idx)) {
+        i_panic("push_notification_event_register(%s): unknown event",
+                event->name);
+    }
+
+    if (array_is_created(&push_notification_events)) {
+        array_delete(&push_notification_events, idx, 1);
+
+        if (array_is_empty(&push_notification_events)) {
+            array_free(&push_notification_events);
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-events.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,127 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_EVENTS_H
+#define PUSH_NOTIFICATION_EVENTS_H
+
+#include "mail-types.h"
+
+struct mail;
+struct mailbox;
+struct push_notification_event_config;
+struct push_notification_driver_txn;
+struct push_notification_txn;
+struct push_notification_txn_event;
+struct push_notification_txn_mbox;
+struct push_notification_txn_msg;
+
+HASH_TABLE_DEFINE_TYPE(push_notification_mq_data, const char *, const char *);
+
+struct push_notification_event_vfuncs_init {
+    /* Return the default config for an event (or NULL if config is
+     * required). */
+    void *(*default_config)(void);
+};
+
+struct push_notification_event_vfuncs_mbox {
+    /* Output debug information about a message event. */
+    void (*debug_mbox)(struct push_notification_txn_event *event);
+        /* Called when message data is about to be free'd. */
+    void (*free_mbox)(struct push_notification_txn_event *event);
+};
+
+struct push_notification_event_vfuncs_mbox_triggers {
+    /* Mailbox event: create mailbox. */
+    void (*create)(struct push_notification_txn *ptxn,
+                   struct push_notification_event_config *ec,
+                   struct push_notification_txn_mbox *mbox);
+    /* Mailbox event: delete mailbox. */
+    void (*delete)(struct push_notification_txn *ptxn,
+                   struct push_notification_event_config *ec,
+                   struct push_notification_txn_mbox *mbox);
+    /* Mailbox event: rename mailbox. */
+    void (*rename)(struct push_notification_txn *ptxn,
+                   struct push_notification_event_config *ec,
+                   struct push_notification_txn_mbox *mbox,
+                   struct mailbox *old);
+    /* Mailbox event: subscribe mailbox. */
+    void (*subscribe)(struct push_notification_txn *ptxn,
+                      struct push_notification_event_config *ec,
+                      struct push_notification_txn_mbox *mbox);
+    /* Mailbox event: unsubscribe mailbox. */
+    void (*unsubscribe)(struct push_notification_txn *ptxn,
+                        struct push_notification_event_config *ec,
+                        struct push_notification_txn_mbox *mbox);
+};
+
+struct push_notification_event_vfuncs_msg {
+    /* Output debug information about a message event. */
+    void (*debug_msg)(struct push_notification_txn_event *event);
+    /* Called when message data is about to be free'd. */
+    void (*free_msg)(struct push_notification_txn_event *event);
+};
+
+struct push_notification_event_vfuncs_msg_triggers {
+    /* Message event: save message (from MTA). */
+    void (*save)(struct push_notification_txn *ptxn,
+                 struct push_notification_event_config *ec,
+                 struct push_notification_txn_msg *msg,
+                 struct mail *mail);
+    /* Message event: append message (from MUA). */
+    void (*append)(struct push_notification_txn *ptxn,
+                   struct push_notification_event_config *ec,
+                   struct push_notification_txn_msg *msg,
+                   struct mail *mail);
+    /* Message event: expunge message. */
+    void (*expunge)(struct push_notification_txn *ptxn,
+                    struct push_notification_event_config *ec,
+                    struct push_notification_txn_msg *msg);
+    /* Message event: flag change. */
+    void (*flagchange)(struct push_notification_txn *ptxn,
+                       struct push_notification_event_config *ec,
+                       struct push_notification_txn_msg *msg,
+                       struct mail *mail,
+                       enum mail_flags old_flags);
+    /* Message event: keyword change. */
+    void (*keywordchange)(struct push_notification_txn *ptxn,
+                          struct push_notification_event_config *ec,
+                          struct push_notification_txn_msg *msg,
+                          struct mail *mail,
+                          const char *const *old_keywords);
+};
+
+struct push_notification_event_config {
+    const struct push_notification_event *event;
+    void *config;
+};
+
+struct push_notification_event {
+    const char *name;
+    struct push_notification_event_vfuncs_init init;
+    struct push_notification_event_vfuncs_mbox mbox;
+    struct push_notification_event_vfuncs_mbox_triggers mbox_triggers;
+    struct push_notification_event_vfuncs_msg msg;
+    struct push_notification_event_vfuncs_msg_triggers msg_triggers;
+};
+
+struct push_notification_txn_event {
+    struct push_notification_event_config *event;
+    void *data;
+};
+
+ARRAY_DEFINE_TYPE(push_notification_event,
+                  const struct push_notification_event *);
+extern ARRAY_TYPE(push_notification_event) push_notification_events;
+
+
+void
+push_notification_event_init(struct push_notification_driver_txn *dtxn,
+                             const char *event_name, void *config);
+
+void push_notification_event_register
+(const struct push_notification_event *event);
+void push_notification_event_unregister
+(const struct push_notification_event *event);
+
+
+#endif	/* PUSH_NOTIFICATION_EVENTS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-plugin.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,327 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-private.h"
+#include "notify-plugin.h"
+#include "str.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-events-rfc5423.h"
+#include "push-notification-plugin.h"
+#include "push-notification-triggers.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+
+#define PUSH_NOTIFICATION_CONFIG "push_notification_driver"
+#define PUSH_NOTIFICATION_CONFIG_OLD "push_notification_backend"
+
+#define PUSH_NOTIFICATION_USER_CONTEXT(obj) \
+        MODULE_CONTEXT(obj, push_notification_user_module)
+static MODULE_CONTEXT_DEFINE_INIT(push_notification_user_module,
+                                  &mail_user_module_register);
+
+
+static struct push_notification_user *puser = NULL;
+
+
+static struct push_notification_txn *
+push_notification_transaction_create(struct mailbox *box,
+                                     struct mailbox_transaction_context *t)
+{
+    struct push_notification_driver_txn *dtxn;
+    struct push_notification_driver_user **duser;
+    pool_t pool;
+    struct push_notification_txn *ptxn;
+    struct mail_storage *storage;
+
+    pool = pool_alloconly_create("push notification transaction", 2048);
+
+    ptxn = p_new(pool, struct push_notification_txn, 1);
+    ptxn->mbox = box;
+    storage = mailbox_get_storage(box);
+    ptxn->muser = mail_storage_get_user(storage);
+    ptxn->pool = pool;
+    ptxn->puser = PUSH_NOTIFICATION_USER_CONTEXT(ptxn->muser);
+    ptxn->t = t;
+    ptxn->trigger = PUSH_NOTIFICATION_EVENT_TRIGGER_NONE;
+
+    p_array_init(&ptxn->drivers, pool, 4);
+
+    if (storage->user->autocreated &&
+        (strcmp(storage->name, "raw") == 0)) {
+        /* no notifications for autocreated raw users */
+        return ptxn;
+    }
+
+    array_foreach_modifiable(&ptxn->puser->drivers, duser) {
+        dtxn = p_new(pool, struct push_notification_driver_txn, 1);
+        dtxn->duser = *duser;
+        dtxn->ptxn = ptxn;
+
+        if ((dtxn->duser->driver->v.begin_txn == NULL) ||
+            dtxn->duser->driver->v.begin_txn(dtxn)) {
+            array_append(&ptxn->drivers, &dtxn, 1);
+        }
+    }
+
+    return ptxn;
+}
+
+static void push_notification_transaction_end
+(struct push_notification_txn *ptxn, bool success)
+{
+    struct push_notification_driver_txn **dtxn;
+
+    array_foreach_modifiable(&ptxn->drivers, dtxn) {
+        if ((*dtxn)->duser->driver->v.end_txn != NULL) {
+            (*dtxn)->duser->driver->v.end_txn(*dtxn, success);
+        }
+    }
+
+    pool_unref(&ptxn->pool);
+}
+
+static void push_notification_transaction_commit
+(void *txn, struct mail_transaction_commit_changes *changes)
+{
+    struct push_notification_txn *ptxn = (struct push_notification_txn *)txn;
+
+    if (changes == NULL) {
+        push_notification_txn_mbox_end(ptxn);
+    } else {
+        push_notification_txn_msg_end(ptxn, changes);
+    }
+
+    push_notification_transaction_end(ptxn, TRUE);
+}
+
+static void push_notification_mailbox_create(struct mailbox *box)
+{
+    struct push_notification_txn *ptxn;
+
+    ptxn = push_notification_transaction_create(box, NULL);
+    push_notification_trigger_mbox_create(ptxn, box, NULL);
+    push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void push_notification_mailbox_delete(void *txn ATTR_UNUSED,
+                                             struct mailbox *box)
+{
+    struct push_notification_txn *ptxn;
+
+    ptxn = push_notification_transaction_create(box, NULL);
+    push_notification_trigger_mbox_delete(ptxn, box, NULL);
+    push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void push_notification_mailbox_rename(struct mailbox *src,
+                                             struct mailbox *dest)
+{
+    struct push_notification_txn *ptxn;
+
+    ptxn = push_notification_transaction_create(dest, NULL);
+    push_notification_trigger_mbox_rename(ptxn, src, dest, NULL);
+    push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void push_notification_mailbox_subscribe(struct mailbox *box,
+                                                bool subscribed)
+{
+    struct push_notification_txn *ptxn;
+
+    ptxn = push_notification_transaction_create(box, NULL);
+    push_notification_trigger_mbox_subscribe(ptxn, box, subscribed, NULL);
+    push_notification_transaction_commit(ptxn, NULL);
+}
+
+static void push_notification_mail_save(void *txn, struct mail *mail)
+{
+    struct push_notification_txn *ptxn = (struct push_notification_txn *)txn;
+
+    /* External means a COPY or APPEND IMAP action. */
+    if (ptxn->t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) {
+        push_notification_trigger_msg_save_append(ptxn, mail, NULL);
+    } else {
+        push_notification_trigger_msg_save_new(ptxn, mail, NULL);
+    }
+}
+
+static void push_notification_mail_copy(void *txn,
+                                        struct mail *src ATTR_UNUSED,
+                                        struct mail *dest)
+{
+    push_notification_trigger_msg_save_append(
+            (struct push_notification_txn *)txn, dest, NULL);
+}
+
+static void push_notification_mail_expunge(void *txn, struct mail *mail)
+{
+    push_notification_trigger_msg_save_expunge(
+            (struct push_notification_txn *)txn, mail, NULL);
+}
+
+static void
+push_notification_mail_update_flags(void *txn, struct mail *mail,
+                                    enum mail_flags old_flags)
+{
+    push_notification_trigger_msg_flag_change(
+            (struct push_notification_txn *) txn, mail, NULL, old_flags);
+}
+
+static void
+push_notification_mail_update_keywords(void *txn, struct mail *mail,
+                                       const char *const *old_keywords)
+{
+    push_notification_trigger_msg_keyword_change(
+            (struct push_notification_txn *) txn, mail, NULL, old_keywords);
+}
+
+static void *
+push_notification_transaction_begin(struct mailbox_transaction_context *t)
+{
+    return push_notification_transaction_create(mailbox_transaction_get_mailbox(t), t);
+}
+
+static void push_notification_transaction_rollback(void *txn)
+{
+    struct push_notification_txn *ptxn = (struct push_notification_txn *)txn;
+
+    push_notification_transaction_end(ptxn, FALSE);
+}
+
+static void
+push_notification_user_created_init_config(const char *config_name,
+                                           struct mail_user *user,
+                                           struct push_notification_user *puser)
+{
+    struct push_notification_driver_user *duser;
+    const char *env;
+    unsigned int i;
+    string_t *root_name;
+
+    root_name = t_str_new(32);
+    str_append(root_name, config_name);
+
+    for (i = 2;; i++) {
+        env = mail_user_plugin_getenv(user, str_c(root_name));
+        if ((env == NULL) || (*env == '\0')) {
+            break;
+        }
+
+        if (push_notification_driver_init(user, env, puser->pool, &duser) < 0) {
+            break;
+        }
+
+        // Add driver.
+        array_append(&puser->drivers, &duser, 1);
+
+        str_truncate(root_name, strlen(config_name));
+        str_printfa(root_name, "%d", i);
+    }
+}
+
+static void push_notification_user_created_init(struct mail_user *user)
+{
+    pool_t pool;
+
+    pool = pool_alloconly_create("push notification plugin", 1024);
+
+    puser = p_new(pool, struct push_notification_user, 1);
+    puser->pool = pool;
+
+    p_array_init(&puser->drivers, pool, 4);
+
+    push_notification_user_created_init_config(PUSH_NOTIFICATION_CONFIG, user,
+                                               puser);
+
+    if (array_is_empty(&puser->drivers)) {
+        /* Support old configuration (it was available at time initial OX
+         * driver was first released. */
+        push_notification_user_created_init_config(PUSH_NOTIFICATION_CONFIG_OLD,
+                                                   user, puser);
+    }
+}
+
+static void push_notification_user_created(struct mail_user *user)
+{
+    if (puser == NULL) {
+        push_notification_user_created_init(user);
+    }
+
+    MODULE_CONTEXT_SET(user, push_notification_user_module, puser);
+}
+
+
+/* Plugin interface. */
+
+const char *push_notification_plugin_version = DOVECOT_ABI_VERSION;
+const char *push_notification_plugin_dependencies[] = { "notify", NULL };
+
+extern struct push_notification_driver push_notification_driver_dlog;
+extern struct push_notification_driver push_notification_driver_ox;
+
+static struct notify_context *push_notification_ctx;
+
+static const struct notify_vfuncs push_notification_vfuncs = {
+    /* Mailbox Events */
+    .mailbox_create = push_notification_mailbox_create,
+    .mailbox_delete_commit = push_notification_mailbox_delete,
+    .mailbox_rename = push_notification_mailbox_rename,
+    .mailbox_set_subscribed = push_notification_mailbox_subscribe,
+
+    /* Mail Events */
+    .mail_copy = push_notification_mail_copy,
+    .mail_save = push_notification_mail_save,
+    .mail_expunge = push_notification_mail_expunge,
+    .mail_update_flags = push_notification_mail_update_flags,
+    .mail_update_keywords = push_notification_mail_update_keywords,
+    .mail_transaction_begin = push_notification_transaction_begin,
+    .mail_transaction_commit = push_notification_transaction_commit,
+    .mail_transaction_rollback = push_notification_transaction_rollback
+};
+
+static struct mail_storage_hooks push_notification_storage_hooks = {
+    .mail_user_created = push_notification_user_created
+};
+
+void push_notification_plugin_init(struct module *module)
+{
+    push_notification_ctx = notify_register(&push_notification_vfuncs);
+    mail_storage_hooks_add(module, &push_notification_storage_hooks);
+
+    push_notification_driver_register(&push_notification_driver_dlog);
+    push_notification_driver_register(&push_notification_driver_ox);
+
+    push_notification_event_register_rfc5423_events();
+}
+
+void push_notification_plugin_deinit(void)
+{
+    struct push_notification_driver_user **duser;
+
+    if (puser != NULL) {
+        array_foreach_modifiable(&puser->drivers, duser) {
+            if ((*duser)->driver->v.deinit != NULL) {
+                (*duser)->driver->v.deinit(*duser);
+            }
+
+            if ((*duser)->driver->v.cleanup != NULL) {
+                (*duser)->driver->v.cleanup();
+            }
+        }
+
+        array_free(&puser->drivers);
+        pool_unref(&puser->pool);
+    }
+
+    push_notification_driver_unregister(&push_notification_driver_dlog);
+    push_notification_driver_unregister(&push_notification_driver_ox);
+
+    mail_storage_hooks_remove(&push_notification_storage_hooks);
+    notify_unregister(push_notification_ctx);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-plugin.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,13 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_PLUGIN_H
+#define PUSH_NOTIFICATION_PLUGIN_H
+
+extern const char *push_notification_plugin_dependencies[];
+
+struct module;
+
+void push_notification_plugin_init(struct module *module);
+void push_notification_plugin_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-triggers.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,222 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+
+#include "mail-storage.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-triggers.h"
+#include "push-notification-txn-mbox.h"
+#include "push-notification-txn-msg.h"
+
+
+static void
+push_notification_trigger_mbox_common(struct push_notification_txn *txn,
+                                      struct mailbox *box,
+                                      struct push_notification_txn_mbox **mbox,
+                                      enum push_notification_event_trigger trigger)
+{
+    if (*mbox == NULL) {
+        *mbox = push_notification_txn_mbox_create(txn, box);
+    }
+
+    txn->trigger |= trigger;
+}
+
+void
+push_notification_trigger_mbox_create(struct push_notification_txn *txn,
+                                      struct mailbox *box,
+                                      struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_mbox_common(txn, box, &mbox,
+                                          PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_CREATE);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->mbox_triggers.create != NULL) {
+                (*ec)->event->mbox_triggers.create(txn, *ec, mbox);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_mbox_delete(struct push_notification_txn *txn,
+                                      struct mailbox *box,
+                                      struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_mbox_common(txn, box, &mbox,
+                                          PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_DELETE);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->mbox_triggers.delete != NULL) {
+                (*ec)->event->mbox_triggers.delete(txn, *ec, mbox);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_mbox_rename(struct push_notification_txn *txn,
+                                      struct mailbox *src,
+                                      struct mailbox *dest,
+                                      struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_mbox_common(txn, dest, &mbox,
+                                          PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_RENAME);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->mbox_triggers.rename != NULL) {
+                (*ec)->event->mbox_triggers.rename(txn, *ec, mbox, src);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_mbox_subscribe(struct push_notification_txn *txn,
+                                         struct mailbox *box,
+                                         bool subscribed,
+                                         struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_mbox_common(txn, box, &mbox,
+                                          PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_SUBSCRIBE);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if (subscribed == TRUE) {
+                if ((*ec)->event->mbox_triggers.subscribe != NULL) {
+                    (*ec)->event->mbox_triggers.subscribe(txn, *ec, mbox);
+                }
+            } else {
+                if ((*ec)->event->mbox_triggers.unsubscribe != NULL) {
+                    (*ec)->event->mbox_triggers.unsubscribe(txn, *ec, mbox);
+                }
+            }
+        }
+    }
+}
+
+static void
+push_notification_trigger_msg_common(struct push_notification_txn *txn,
+                                     struct mail *mail,
+                                     struct push_notification_txn_msg **msg,
+                                     enum push_notification_event_trigger trigger)
+{
+    if (*msg == NULL) {
+        *msg = push_notification_txn_msg_create(txn, mail);
+    }
+
+    txn->trigger |= trigger;
+}
+
+void
+push_notification_trigger_msg_save_new(struct push_notification_txn *txn,
+                                       struct mail *mail,
+                                       struct push_notification_txn_msg *msg)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_msg_common(txn, mail, &msg,
+                                         PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_NEW);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->msg_triggers.save != NULL) {
+                (*ec)->event->msg_triggers.save(txn, *ec, msg, mail);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_msg_save_append(struct push_notification_txn *txn,
+                                          struct mail *mail,
+                                          struct push_notification_txn_msg *msg)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_msg_common(txn, mail, &msg,
+                                         PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_APPEND);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->msg_triggers.append != NULL) {
+                (*ec)->event->msg_triggers.append(txn, *ec, msg, mail);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_msg_save_expunge(struct push_notification_txn *txn,
+                                           struct mail *mail,
+                                           struct push_notification_txn_msg *msg)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_msg_common(txn, mail, &msg,
+                                         PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_EXPUNGE);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->msg_triggers.expunge != NULL) {
+                (*ec)->event->msg_triggers.expunge(txn, *ec, msg);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_msg_flag_change(struct push_notification_txn *txn,
+                                          struct mail *mail,
+                                          struct push_notification_txn_msg *msg,
+                                          enum mail_flags old_flags)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_msg_common(txn, mail, &msg,
+                                         PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_FLAGCHANGE);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->msg_triggers.flagchange != NULL) {
+                (*ec)->event->msg_triggers.flagchange(txn, *ec, msg, mail,
+                                                      old_flags);
+            }
+        }
+    }
+}
+
+void
+push_notification_trigger_msg_keyword_change(struct push_notification_txn *txn,
+                                             struct mail *mail,
+                                             struct push_notification_txn_msg *msg,
+                                             const char *const *old_keywords)
+{
+    struct push_notification_event_config **ec;
+
+    push_notification_trigger_msg_common(txn, mail, &msg,
+                                         PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_KEYWORDCHANGE);
+
+    if (array_is_created(&txn->events)) {
+        array_foreach_modifiable(&txn->events, ec) {
+            if ((*ec)->event->msg_triggers.keywordchange != NULL) {
+                (*ec)->event->msg_triggers.keywordchange(txn, *ec, msg, mail,
+                                                         old_keywords);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-triggers.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,78 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_TRIGGERS_H
+#define PUSH_NOTIFICATION_TRIGGERS_H
+
+#include "mail-types.h"
+
+struct mail;
+struct mailbox;
+struct push_notification_txn;
+struct push_notification_txn_mbox;
+struct push_notification_txn_msg;
+
+
+enum push_notification_event_trigger {
+    PUSH_NOTIFICATION_EVENT_TRIGGER_NONE,
+
+    /* Mailbox actions */
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_CREATE       = 0x001,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_DELETE       = 0x002,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_RENAME       = 0x004,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MBOX_SUBSCRIBE    = 0x008,
+
+    /* Message actions */
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_NEW      = 0x010,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_SAVE_APPEND   = 0x020,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_EXPUNGE       = 0x040,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_FLAGCHANGE    = 0x080,
+    PUSH_NOTIFICATION_EVENT_TRIGGER_MSG_KEYWORDCHANGE = 0x100,
+};
+
+/* Mailbox actions. */
+void
+push_notification_trigger_mbox_create(struct push_notification_txn *txn,
+                                      struct mailbox *box,
+                                      struct push_notification_txn_mbox *mbox);
+void
+push_notification_trigger_mbox_delete(struct push_notification_txn *txn,
+                                      struct mailbox *box,
+                                      struct push_notification_txn_mbox *mbox);
+void
+push_notification_trigger_mbox_rename(struct push_notification_txn *txn,
+                                      struct mailbox *src,
+                                      struct mailbox *dest,
+                                      struct push_notification_txn_mbox *mbox);
+void
+push_notification_trigger_mbox_subscribe(struct push_notification_txn *txn,
+                                         struct mailbox *box,
+                                         bool subscribed,
+                                         struct push_notification_txn_mbox *mbox);
+
+/* Message actions. */
+void
+push_notification_trigger_msg_save_new(struct push_notification_txn *txn,
+                                       struct mail *mail,
+                                       struct push_notification_txn_msg *msg);
+void
+push_notification_trigger_msg_save_append(struct push_notification_txn *txn,
+                                          struct mail *mail,
+                                          struct push_notification_txn_msg *msg);
+void
+push_notification_trigger_msg_save_expunge(struct push_notification_txn *txn,
+                                           struct mail *mail,
+                                           struct push_notification_txn_msg *msg);
+void
+push_notification_trigger_msg_flag_change(struct push_notification_txn *txn,
+                                          struct mail *mail,
+                                          struct push_notification_txn_msg *msg,
+                                          enum mail_flags old_flags);
+void
+push_notification_trigger_msg_keyword_change(struct push_notification_txn *txn,
+                                             struct mail *mail,
+                                             struct push_notification_txn_msg *msg,
+                                             const char *const *old_keywords);
+
+
+#endif /* PUSH_NOTIFICATION_TRIGGERS_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-txn-mbox.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,90 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "mail-storage-private.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-mbox.h"
+
+
+struct push_notification_txn_mbox *
+push_notification_txn_mbox_create(struct push_notification_txn *txn,
+                                  struct mailbox *box)
+{
+    if (txn->mbox_txn == NULL) {
+        txn->mbox_txn = p_new(txn->pool, struct push_notification_txn_mbox, 1);
+        txn->mbox_txn->mailbox = mailbox_get_vname(box);
+    }
+
+    return txn->mbox_txn;
+}
+
+void
+push_notification_txn_mbox_end(struct push_notification_txn *ptxn)
+{
+    struct push_notification_driver_txn **dtxn;
+
+    if (ptxn->mbox_txn != NULL) {
+        array_foreach_modifiable(&ptxn->drivers, dtxn) {
+            if ((*dtxn)->duser->driver->v.process_mbox != NULL) {
+                (*dtxn)->duser->driver->v.process_mbox(*dtxn, ptxn->mbox_txn);
+            }
+        }
+
+        push_notification_txn_mbox_deinit_eventdata(ptxn->mbox_txn);
+    }
+}
+
+void *
+push_notification_txn_mbox_get_eventdata(struct push_notification_txn_mbox *mbox,
+                                         const char *event_name)
+{
+    struct push_notification_txn_event **mevent;
+
+    if (array_is_created(&mbox->eventdata)) {
+        array_foreach_modifiable(&mbox->eventdata, mevent) {
+            if (strcmp((*mevent)->event->event->name, event_name) == 0) {
+                return (*mevent)->data;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+void
+push_notification_txn_mbox_set_eventdata(struct push_notification_txn *txn,
+                                         struct push_notification_txn_mbox *mbox,
+                                         struct push_notification_event_config *event,
+                                         void *data)
+{
+    struct push_notification_txn_event *mevent;
+
+    if (!array_is_created(&mbox->eventdata)) {
+        p_array_init(&mbox->eventdata, txn->pool, 4);
+    }
+
+    mevent = p_new(txn->pool, struct push_notification_txn_event, 1);
+    mevent->data = data;
+    mevent->event = event;
+
+    array_append(&mbox->eventdata, &mevent, 1);
+}
+
+void
+push_notification_txn_mbox_deinit_eventdata
+(struct push_notification_txn_mbox *mbox)
+{
+    struct push_notification_txn_event **mevent;
+
+    if (array_is_created(&mbox->eventdata)) {
+        array_foreach_modifiable(&mbox->eventdata, mevent) {
+            if (((*mevent)->data != NULL) &&
+                ((*mevent)->event->event->mbox.free_mbox != NULL)) {
+                (*mevent)->event->event->mbox.free_mbox(*mevent);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-txn-mbox.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,34 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_TXN_MBOX_H
+#define PUSH_NOTIFICATION_TXN_MBOX_H
+
+
+struct push_notification_txn_event;
+
+struct push_notification_txn_mbox {
+    const char *mailbox;
+
+    ARRAY(struct push_notification_txn_event *) eventdata;
+};
+
+
+struct push_notification_txn_mbox *
+push_notification_txn_mbox_create(struct push_notification_txn *txn,
+                                  struct mailbox *box);
+void
+push_notification_txn_mbox_end(struct push_notification_txn *ptxn);
+
+void *
+push_notification_txn_mbox_get_eventdata(struct push_notification_txn_mbox *mbox,
+                                         const char *event_name);
+void
+push_notification_txn_mbox_set_eventdata(struct push_notification_txn *txn,
+                                         struct push_notification_txn_mbox *mbox,
+                                         struct push_notification_event_config *event,
+                                         void *data);
+void
+push_notification_txn_mbox_deinit_eventdata(struct push_notification_txn_mbox *mbox);
+
+
+#endif	/* PUSH_NOTIFICATION_TXN_MBOX_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-txn-msg.c	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,135 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "mail-storage-private.h"
+
+#include "push-notification-drivers.h"
+#include "push-notification-events.h"
+#include "push-notification-txn-msg.h"
+
+
+struct push_notification_txn_msg *
+push_notification_txn_msg_create(struct push_notification_txn *txn,
+                                 struct mail *mail)
+{
+    struct push_notification_txn_msg *msg = NULL;
+
+    if (hash_table_is_created(txn->messages)) {
+        msg = hash_table_lookup(txn->messages,
+                                POINTER_CAST(txn->t->save_count + 1));
+    } else {
+        hash_table_create_direct(&txn->messages, txn->pool, 4);
+    }
+
+    if (msg == NULL) {
+        msg = p_new(txn->pool, struct push_notification_txn_msg, 1);
+        msg->mailbox = mailbox_get_vname(mail->box);
+        /* Save sequence number - used to determine UID later. */
+        msg->seq = txn->t->save_count;
+        msg->uid = mail->uid;
+
+        hash_table_insert(txn->messages, POINTER_CAST(txn->t->save_count + 1),
+                          msg);
+    }
+
+    return msg;
+}
+
+void
+push_notification_txn_msg_end(struct push_notification_txn *ptxn,
+                              struct mail_transaction_commit_changes *changes)
+{
+    struct hash_iterate_context *hiter;
+    void *key;
+    struct push_notification_driver_txn **dtxn;
+    struct seq_range_iter siter;
+    struct mailbox_status status;
+    uint32_t uid;
+    struct push_notification_txn_msg *value;
+
+    if (!hash_table_is_created(ptxn->messages)) {
+        return;
+    }
+
+    hiter = hash_table_iterate_init(ptxn->messages);
+    seq_range_array_iter_init(&siter, &changes->saved_uids);
+
+    while (hash_table_iterate(hiter, ptxn->messages, &key, &value)) {
+        if (value->uid == 0) {
+            if (seq_range_array_iter_nth(&siter, value->seq, &uid)) {
+                value->uid = uid;
+            }
+        }
+
+        /* uid_validity is only set in changes if message is new. */
+        if (changes->uid_validity == 0) {
+            mailbox_get_open_status(ptxn->mbox, STATUS_UIDVALIDITY, &status);
+            value->uid_validity = status.uidvalidity;
+        } else {
+            value->uid_validity = changes->uid_validity;
+        }
+
+        array_foreach_modifiable(&ptxn->drivers, dtxn) {
+            if ((*dtxn)->duser->driver->v.process_msg != NULL) {
+                (*dtxn)->duser->driver->v.process_msg(*dtxn, value);
+            }
+        }
+
+        push_notification_txn_msg_deinit_eventdata(value);
+    }
+
+    hash_table_iterate_deinit(&hiter);
+    hash_table_destroy(&ptxn->messages);
+}
+
+void *
+push_notification_txn_msg_get_eventdata(struct push_notification_txn_msg *msg,
+                                        const char *event_name)
+{
+    struct push_notification_txn_event **mevent;
+
+    if (array_is_created(&msg->eventdata)) {
+        array_foreach_modifiable(&msg->eventdata, mevent) {
+            if (strcmp((*mevent)->event->event->name, event_name) == 0) {
+                return (*mevent)->data;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+void
+push_notification_txn_msg_set_eventdata(struct push_notification_txn *txn,
+                                        struct push_notification_txn_msg *msg,
+                                        struct push_notification_event_config *event,
+                                        void *data)
+{
+    struct push_notification_txn_event *mevent;
+
+    if (!array_is_created(&msg->eventdata)) {
+        p_array_init(&msg->eventdata, txn->pool, 4);
+    }
+
+    mevent = p_new(txn->pool, struct push_notification_txn_event, 1);
+    mevent->data = data;
+    mevent->event = event;
+
+    array_append(&msg->eventdata, &mevent, 1);
+}
+
+void
+push_notification_txn_msg_deinit_eventdata(struct push_notification_txn_msg *msg)
+{
+    struct push_notification_txn_event **mevent;
+
+    if (array_is_created(&msg->eventdata)) {
+        array_foreach_modifiable(&msg->eventdata, mevent) {
+            if (((*mevent)->data != NULL) &&
+                ((*mevent)->event->event->msg.free_msg != NULL)) {
+                (*mevent)->event->event->msg.free_msg(*mevent);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/push-notification/push-notification-txn-msg.h	Tue Sep 22 22:33:41 2015 -0600
@@ -0,0 +1,43 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#ifndef PUSH_NOTIFICATION_TXN_MSG_H
+#define PUSH_NOTIFICATION_TXN_MSG_H
+
+
+struct mail_transaction_commit_changes;
+struct push_notification_event_config;
+struct push_notification_txn;
+struct push_notification_txn_event;
+
+struct push_notification_txn_msg {
+    const char *mailbox;
+    uint32_t uid;
+    uint32_t uid_validity;
+
+    ARRAY(struct push_notification_txn_event *) eventdata;
+
+    /* Private */
+    unsigned int seq;
+};
+
+
+struct push_notification_txn_msg *
+push_notification_txn_msg_create(struct push_notification_txn *txn,
+                                 struct mail *mail);
+void
+push_notification_txn_msg_end(struct push_notification_txn *ptxn,
+                              struct mail_transaction_commit_changes *changes);
+
+void *
+push_notification_txn_msg_get_eventdata(struct push_notification_txn_msg *msg,
+                                        const char *event_name);
+void
+push_notification_txn_msg_set_eventdata(struct push_notification_txn *txn,
+                                        struct push_notification_txn_msg *msg,
+                                        struct push_notification_event_config *event,
+                                        void *data);
+void
+push_notification_txn_msg_deinit_eventdata(struct push_notification_txn_msg *msg);
+
+
+#endif	/* PUSH_NOTIFICATION_TXN_MSG_H */