diff --git a/include/crm/common/alerts_internal.h b/include/crm/common/alerts_internal.h
index c3c39c2375..c7209f5fd1 100644
--- a/include/crm/common/alerts_internal.h
+++ b/include/crm/common/alerts_internal.h
@@ -1,112 +1,115 @@
 /*
  * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #ifndef ALERT_INTERNAL_H
 #define ALERT_INTERNAL_H
+
+#include <glib.h>
+
 /* Default-Timeout to use before killing a alerts script (in milliseconds) */
 #  define CRM_ALERT_DEFAULT_TIMEOUT_MS (30000)
 
 /* Default-Format-String used to pass timestamps to the alerts scripts */
 #  define CRM_ALERT_DEFAULT_TSTAMP_FORMAT "%H:%M:%S.%06N"
 
 typedef struct {
     char *name;
     char *value;
 }  crm_alert_envvar_t;
 
 enum crm_alert_flags {
     crm_alert_none         = 0x0000,
     crm_alert_node         = 0x0001,
     crm_alert_fencing      = 0x0002,
     crm_alert_resource     = 0x0004,
     crm_alert_attribute    = 0x0008,
     crm_alert_default      = crm_alert_node|crm_alert_fencing|crm_alert_resource
 };
 
 typedef struct {
     char *id;
     char *path;
     char *tstamp_format;
     char *recipient;
     char **select_attribute_name;
-    GListPtr envvars;
+    GHashTable *envvars;
     int timeout;
     uint32_t flags;
 } crm_alert_entry_t;
 
 enum crm_alert_keys_e {
     CRM_alert_recipient = 0,
     CRM_alert_node,
     CRM_alert_nodeid,
     CRM_alert_rsc,
     CRM_alert_task,
     CRM_alert_interval,
     CRM_alert_desc,
     CRM_alert_status,
     CRM_alert_target_rc,
     CRM_alert_rc,
     CRM_alert_kind,
     CRM_alert_version,
     CRM_alert_node_sequence,
     CRM_alert_timestamp,
     CRM_alert_attribute_name,
     CRM_alert_attribute_value,
     CRM_alert_select_kind,
     CRM_alert_select_attribute_name
 };
 
 #define CRM_ALERT_INTERNAL_KEY_MAX 16
 #define CRM_ALERT_KEY_PATH "CRM_alert_path"
 #define CRM_ALERT_NODE_SEQUENCE "CRM_alert_node_sequence"
 
 extern guint crm_alert_max_alert_timeout;
 extern const char *crm_alert_keys[CRM_ALERT_INTERNAL_KEY_MAX][3];
 
 crm_alert_entry_t *crm_dup_alert_entry(crm_alert_entry_t *entry);
 crm_alert_envvar_t *crm_dup_alert_envvar(crm_alert_envvar_t *src);
+crm_alert_entry_t *crm_alert_entry_new(const char *id, const char *path);
 void crm_free_alert_entry(crm_alert_entry_t *entry);
 void crm_free_alert_envvar(crm_alert_envvar_t *entry);
 void crm_set_alert_key(enum crm_alert_keys_e name, const char *value);
 void crm_set_alert_key_int(enum crm_alert_keys_e name, int value);
 void crm_insert_alert_key(GHashTable *table, enum crm_alert_keys_e name,
                           const char *value);
 void crm_insert_alert_key_int(GHashTable *table, enum crm_alert_keys_e name,
                               int value);
 void crm_unset_alert_keys(void);
 void crm_set_envvar_list(crm_alert_entry_t *entry);
 void crm_unset_envvar_list(crm_alert_entry_t *entry);
 gboolean crm_is_target_alert(char **list, const char *value);
 
 static inline const char *
 crm_alert_flag2text(enum crm_alert_flags flag)
 {
     switch (flag) {
         case crm_alert_node:
             return "node";
         case crm_alert_fencing:
             return "fencing";
         case crm_alert_resource:
             return "resource";
         case crm_alert_attribute:
             return "attribute";
         default:
             return "unknown";
     }
 }
-
 #endif
diff --git a/lib/common/alerts.c b/lib/common/alerts.c
index 44130c2071..051d9f7249 100644
--- a/lib/common/alerts.c
+++ b/lib/common/alerts.c
@@ -1,259 +1,255 @@
 /*
  * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 #include <crm/crm.h>
 #include <crm/lrmd.h>
 #include <crm/msg_xml.h>
 #include <crm/common/alerts_internal.h>
 
 guint crm_alert_max_alert_timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
 
 /*		
  * to allow script compatibility we can have more than one		
  * set of environment variables		
  */
 const char *crm_alert_keys[CRM_ALERT_INTERNAL_KEY_MAX][3] =		
 {		
     [CRM_alert_recipient]     = {"CRM_notify_recipient",     "CRM_alert_recipient",     NULL},		
     [CRM_alert_node]          = {"CRM_notify_node",          "CRM_alert_node",          NULL},		
     [CRM_alert_nodeid]        = {"CRM_notify_nodeid",        "CRM_alert_nodeid",        NULL},		
     [CRM_alert_rsc]           = {"CRM_notify_rsc",           "CRM_alert_rsc",           NULL},		
     [CRM_alert_task]          = {"CRM_notify_task",          "CRM_alert_task",          NULL},		
     [CRM_alert_interval]      = {"CRM_notify_interval",      "CRM_alert_interval",      NULL},		
     [CRM_alert_desc]          = {"CRM_notify_desc",          "CRM_alert_desc",          NULL},		
     [CRM_alert_status]        = {"CRM_notify_status",        "CRM_alert_status",        NULL},		
     [CRM_alert_target_rc]     = {"CRM_notify_target_rc",     "CRM_alert_target_rc",     NULL},		
     [CRM_alert_rc]            = {"CRM_notify_rc",            "CRM_alert_rc",            NULL},		
     [CRM_alert_kind]          = {"CRM_notify_kind",          "CRM_alert_kind",          NULL},		
     [CRM_alert_version]       = {"CRM_notify_version",       "CRM_alert_version",       NULL},		
     [CRM_alert_node_sequence] = {"CRM_notify_node_sequence", CRM_ALERT_NODE_SEQUENCE, NULL},		
     [CRM_alert_timestamp]     = {"CRM_notify_timestamp",     "CRM_alert_timestamp",     NULL},
     [CRM_alert_attribute_name]     = {"CRM_notify_attribute_name",     "CRM_alert_attribute_name",     NULL},
     [CRM_alert_attribute_value]     = {"CRM_notify_attribute_value",     "CRM_alert_attribute_value",     NULL}
 };
 
 void
 crm_free_alert_envvar(crm_alert_envvar_t *entry)
 {		
     free(entry->name);		
     free(entry->value);		
     free(entry);		
 }		
 
+/*!
+ * \brief Create a new alert entry structure
+ *
+ * \param[in] id  ID to use
+ * \param[in] path  Path to alert agent executable
+ *
+ * \return Pointer to newly allocated alert entry
+ * \note Non-string fields will be filled in with defaults.
+ *       It is the caller's responsibility to free the result,
+ *       using crm_free_alert_entry().
+ */
+crm_alert_entry_t *
+crm_alert_entry_new(const char *id, const char *path)
+{
+    crm_alert_entry_t *entry = calloc(1, sizeof(crm_alert_entry_t));
+
+    CRM_ASSERT(entry);
+    entry->id = strdup(id);
+    entry->path = strdup(path);
+    entry->timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
+    entry->flags = crm_alert_default;
+    return entry;
+}
+
 void
 crm_free_alert_entry(crm_alert_entry_t *entry)
 {		
     free(entry->id);		
     free(entry->path);		
     free(entry->tstamp_format);		
     free(entry->recipient);		
 
     if(entry->select_attribute_name) {
         g_strfreev(entry->select_attribute_name);
     }
 
     if (entry->envvars) {		
-        g_list_free_full(entry->envvars,		
-                         (GDestroyNotify) crm_free_alert_envvar);
+        g_hash_table_destroy(entry->envvars);
     }		
     free(entry);		
 }		
 
 crm_alert_envvar_t *
 crm_dup_alert_envvar(crm_alert_envvar_t *src)
 {		
     crm_alert_envvar_t *dst = calloc(1, sizeof(crm_alert_envvar_t));		
 
     CRM_ASSERT(dst);		
     dst->name = strdup(src->name);		
     dst->value = src->value?strdup(src->value):NULL;		
     return dst;		
 }		
 
-static GListPtr		
-copy_envvar_list_remove_dupes(crm_alert_entry_t *entry)		
-{		
-    GListPtr dst = NULL, ls, ld;		
-
-    /* we are adding to the front so variable dupes coming via		
-     * recipient-section have got precedence over those in the		
-     * global section - we don't expect that many variables here		
-     * that it pays off to go for a hash-table to make dupe elimination		
-     * more efficient - maybe later when we might decide to do more		
-     * with the variables than cycling through them		
-     */		
-
-    for (ls = g_list_first(entry->envvars); ls; ls = g_list_next(ls)) {		
-        for (ld = g_list_first(dst); ld; ld = g_list_next(ld)) {		
-            if (!strcmp(((crm_alert_envvar_t *)(ls->data))->name,		
-                        ((crm_alert_envvar_t *)(ld->data))->name)) {		
-                break;		
-            }		
-        }		
-        if (!ld) {		
-            dst = g_list_prepend(dst, crm_dup_alert_envvar((crm_alert_envvar_t *)(ls->data)));
-        }		
-    }		
-
-    return dst;		
-}		
-
 /*!
  * \internal
  * \brief Duplicate an alert entry
  *
  * \param[in] entry  Alert entry to duplicate
  *
  * \return Duplicate of alert entry
  */
 crm_alert_entry_t *
 crm_dup_alert_entry(crm_alert_entry_t *entry)
-{		
-    crm_alert_entry_t *new_entry =		
-        (crm_alert_entry_t *) calloc(1, sizeof(crm_alert_entry_t));		
+{
+    crm_alert_entry_t *new_entry = crm_alert_entry_new(entry->id, entry->path);
 
-    CRM_ASSERT(new_entry);		
-    *new_entry = (crm_alert_entry_t) {		
-        .id = strdup(entry->id),		
-        .path = strdup(entry->path),		
-        .timeout = entry->timeout,		
-        .tstamp_format = entry->tstamp_format?strdup(entry->tstamp_format):NULL,		
-        .recipient = entry->recipient?strdup(entry->recipient):NULL,		
-        .flags = entry->flags,
-        .select_attribute_name = entry->select_attribute_name?g_strdupv(entry->select_attribute_name):NULL,		
-        .envvars = entry->envvars?		
-            copy_envvar_list_remove_dupes(entry)		
-            :NULL		
-    };		
+    new_entry->timeout = entry->timeout;
+    new_entry->flags = entry->flags;
+    new_entry->envvars = crm_str_table_dup(entry->envvars);
+    if (entry->tstamp_format) {
+        new_entry->tstamp_format = strdup(entry->tstamp_format);
+    }
+    if (entry->recipient) {
+        new_entry->recipient = strdup(entry->recipient);
+    }
+    if (entry->select_attribute_name) {
+        new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name);
+    }
     return new_entry;
-}		
+}
 
 void
 crm_set_alert_key(enum crm_alert_keys_e name, const char *value)
 {
     const char **key;
 
     for (key = crm_alert_keys[name]; *key; key++) {
         crm_trace("Setting alert key %s = '%s'", *key, value);
         if (value) {
             setenv(*key, value, 1);
         } else {
             unsetenv(*key);
         }
     }
 }
 
 void
 crm_set_alert_key_int(enum crm_alert_keys_e name, int value)
 {
     char *s = crm_itoa(value);
 
     crm_set_alert_key(name, s);
     free(s);
 }
 
 void
 crm_unset_alert_keys()
 {
     const char **key;
     enum crm_alert_keys_e name;
 
     for(name = 0; name < DIMOF(crm_alert_keys); name++) {
         for(key = crm_alert_keys[name]; *key; key++) {
             crm_trace("Unsetting alert key %s", *key);
             unsetenv(*key);
         }
     }
 }
 
 void
 crm_insert_alert_key(GHashTable *table, enum crm_alert_keys_e name,
                      const char *value)
 {
     for (const char **key = crm_alert_keys[name]; *key; key++) {
         crm_trace("Inserting alert key %s = '%s'", *key, value);
         if (value) {
             g_hash_table_insert(table, strdup(*key), strdup(value));
         } else {
             g_hash_table_remove(table, *key);
         }
     }
 }
 
 void
 crm_insert_alert_key_int(GHashTable *table, enum crm_alert_keys_e name,
                          int value)
 {
     for (const char **key = crm_alert_keys[name]; *key; key++) {
         crm_trace("Inserting alert key %s = %d", *key, value);
         g_hash_table_insert(table, strdup(*key), crm_itoa(value));
     }
 }
 
+static void
+set_envvar(gpointer key, gpointer value, gpointer user_data)
+{
+    gboolean always_unset = GPOINTER_TO_INT(user_data);
+
+    crm_trace("%s environment variable %s='%s'",
+              (value? "Setting" : "Unsetting"),
+              (char*)key, (value? (char*)value : ""));
+    if (value && !always_unset) {
+        setenv(key, value, 1);
+    } else {
+        unsetenv(key);
+    }
+}
+
 void
 crm_set_envvar_list(crm_alert_entry_t *entry)
 {
-    GListPtr l;
-
-    for (l = g_list_first(entry->envvars); l; l = g_list_next(l)) {
-        crm_alert_envvar_t *env = (crm_alert_envvar_t *)(l->data);
-
-        crm_trace("Setting environment variable %s = '%s'", env->name,
-                  env->value?env->value:"");
-        if (env->value) {
-            setenv(env->name, env->value, 1);
-        } else {
-            unsetenv(env->name);
-        }
+    if (entry->envvars) {
+        g_hash_table_foreach(entry->envvars, set_envvar, GINT_TO_POINTER(FALSE));
     }
 }
 
+/*
+ * \note We have no way of restoring a previous value if one was set.
+ */
 void
 crm_unset_envvar_list(crm_alert_entry_t *entry)
 {
-    GListPtr l;
-
-    for (l = g_list_first(entry->envvars); l; l = g_list_next(l)) {
-        crm_alert_envvar_t *env = (crm_alert_envvar_t *)(l->data);
-
-        crm_trace("Unsetting environment variable %s", env->name);
-        unsetenv(env->name);
+    if (entry->envvars) {
+        g_hash_table_foreach(entry->envvars, set_envvar, GINT_TO_POINTER(TRUE));
     }
 }
 
 gboolean 
 crm_is_target_alert(char **list, const char *value)
 {
     int target_list_num = 0;
     gboolean rc = FALSE;
 
     if (list == NULL) return TRUE;
 
     target_list_num = g_strv_length(list);
 
     for( int cnt = 0; cnt < target_list_num; cnt++ ) {
         if (strcmp(list[cnt], value) == 0) {
             rc = TRUE;
             break;
         }
     } 
 
     return rc;
 }
 
diff --git a/lib/lrmd/lrmd_alerts.c b/lib/lrmd/lrmd_alerts.c
index e6c5c503a8..840492f99d 100644
--- a/lib/lrmd/lrmd_alerts.c
+++ b/lib/lrmd/lrmd_alerts.c
@@ -1,63 +1,68 @@
 /*
  * Copyright (c) 2015 David Vossel <davidvossel@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  * 
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  * 
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
 #include <crm_internal.h>
 
 #include <glib.h>
 #include <unistd.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/services.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/alerts_internal.h>
 #include <crm/lrmd_alerts_internal.h>
 
 #include <crm/pengine/status.h>
 #include <crm/cib.h>
 #include <crm/lrmd.h>
 
 lrmd_key_value_t *
 lrmd_set_alert_key_to_lrmd_params(lrmd_key_value_t *head, enum crm_alert_keys_e name, const char *value)
 {
     const char **key;
 
     for (key = crm_alert_keys[name]; *key; key++) {
         crm_trace("Setting alert key %s = '%s'", *key, value);
         head = lrmd_key_value_add(head, *key, value);
     }
     return head;
 }
 
-lrmd_key_value_t *
-lrmd_set_alert_envvar_to_lrmd_params(lrmd_key_value_t *head, crm_alert_entry_t *entry)
+static void
+set_ev_kv(gpointer key, gpointer value, gpointer user_data)
 {
-    GListPtr l;
+    lrmd_key_value_t **head = (lrmd_key_value_t **) user_data;
 
-    for (l = g_list_first(entry->envvars); l; l = g_list_next(l)) {
-        crm_alert_envvar_t *ev = (crm_alert_envvar_t *)(l->data);
+    if (value) {
+        crm_trace("Setting environment variable %s='%s'",
+                  (char*)key, (char*)value);
+        *head = lrmd_key_value_add(*head, key, value);
+    }
+}
 
-        if (ev->value) {
-            crm_trace("Setting environment variable %s='%s'",
-                      ev->name, ev->value);
-            head = lrmd_key_value_add(head, ev->name, ev->value);
-        }
+lrmd_key_value_t *
+lrmd_set_alert_envvar_to_lrmd_params(lrmd_key_value_t *head,
+                                     crm_alert_entry_t *entry)
+{
+    if (entry->envvars) {
+        g_hash_table_foreach(entry->envvars, set_ev_kv, &head);
     }
     return head;
 }
diff --git a/lib/pengine/rules_alerts.c b/lib/pengine/rules_alerts.c
index 12686c193e..21fd50d615 100644
--- a/lib/pengine/rules_alerts.c
+++ b/lib/pengine/rules_alerts.c
@@ -1,285 +1,253 @@
 /*
  * Copyright (C) 2015-2017 Andrew Beekhof <andrew@beekhof.net>
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/pengine/rules.h>
 #include <crm/common/alerts_internal.h>
 #include <crm/pengine/rules_internal.h>
 
 #ifdef RHEL7_COMPAT
 /* @COMPAT An early implementation of alerts was backported to RHEL 7,
  * even though it was never in an upstream release.
  */
 static char *notify_script = NULL;
 static char *notify_target = NULL;
 
 void
 pe_enable_legacy_alerts(const char *script, const char *target)
 {
     free(notify_script);
     notify_script = (script && strcmp(script, "/dev/null"))?
                     strdup(script) : NULL;
 
     free(notify_target);
     notify_target = target? strdup(target): NULL;
 }
 #endif
 
-static GHashTable *
+static void
 get_meta_attrs_from_cib(xmlNode *basenode, crm_alert_entry_t *entry,
                         guint *max_timeout)
 {
     GHashTable *config_hash = crm_str_table_new();
     crm_time_t *now = crm_time_new(NULL);
     const char *value = NULL;
 
     unpack_instance_attributes(basenode, basenode, XML_TAG_META_SETS, NULL,
                                config_hash, NULL, FALSE, now);
+    crm_time_free(now);
 
     value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TIMEOUT);
     if (value) {
         entry->timeout = crm_get_msec(value);
         if (entry->timeout <= 0) {
             if (entry->timeout == 0) {
-                crm_trace("Setting timeout to default %dmsec",
-                          CRM_ALERT_DEFAULT_TIMEOUT_MS);
+                crm_trace("Alert %s uses default timeout of %dmsec",
+                          entry->id, CRM_ALERT_DEFAULT_TIMEOUT_MS);
             } else {
-                crm_warn("Invalid timeout value setting to default %dmsec",
-                         CRM_ALERT_DEFAULT_TIMEOUT_MS);
+                crm_warn("Alert %s has invalid timeout value '%s', using default %dmsec",
+                         entry->id, (char*)value, CRM_ALERT_DEFAULT_TIMEOUT_MS);
             }
             entry->timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
         } else {
-            crm_trace("Found timeout %dmsec", entry->timeout);
+            crm_trace("Alert %s uses timeout of %dmsec",
+                      entry->id, entry->timeout);
         }
         if (entry->timeout > *max_timeout) {
             *max_timeout = entry->timeout;
         }
     }
     value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TSTAMP_FORMAT);
     if (value) {
         /* hard to do any checks here as merely anything can
          * can be a valid time-format-string
          */
-        entry->tstamp_format = (char *) value;
-        crm_trace("Found timestamp format string '%s'", value);
+        entry->tstamp_format = strdup(value);
+        crm_trace("Alert %s uses timestamp format '%s'",
+                  entry->id, entry->tstamp_format);
     }
 
     value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_SELECT_KIND);
     if (value) {
         int n = 0;
         uint32_t flags = crm_alert_none;
 
         crm_debug("Alert %s has event filter: %s", entry->id, value);
         while (*value != 0) {
             while (*value == ',') {
                 ++value;
             }
             n = 0;
             while ((value[n] != ',') && (value[n] != 0)) {
                 ++n;
             }
             if (!strncmp(value, "node", n)) {
                 flags |= crm_alert_node;
             } else if (!strncmp(value, "fencing", n)) {
                 flags |= crm_alert_fencing;
             } else if (!strncmp(value, "resource", n)) {
                 flags |= crm_alert_resource;
             } else if (!strncmp(value, "attribute", n)) {
                 flags |= crm_alert_attribute;
             } else {
                 crm_warn("Unrecognized alert type '%s' for %s", value, entry->id);
             }
             value += n;
         }
         if (flags) {
             entry->flags = flags;
         }
     }
 
     value = g_hash_table_lookup(config_hash,
                                 XML_ALERT_ATTR_SELECT_ATTRIBUTE_NAME);
     if (value) {
         crm_debug("Alert %s has attribute filter: %s", entry->id, value);
-        entry->select_attribute_name = g_strsplit((char*) value, ",", 0);
-        crm_trace("Found attribute_name string '%s'", (char *) value);
+        entry->select_attribute_name = g_strsplit(value, ",", 0);
     }
 
-    crm_time_free(now);
-    return config_hash; /* keep hash as long as strings are needed */
+    g_hash_table_destroy(config_hash);
 }
 
 static void
-drop_envvars(crm_alert_entry_t *entry, int count)
-{
-    int i;
-
-    for (i = 0; entry->envvars && ((count < 0) || (i < count)); i++) {
-        GListPtr first = g_list_first(entry->envvars);
-
-        crm_free_alert_envvar((crm_alert_envvar_t *) first->data);
-        entry->envvars = g_list_delete_link(entry->envvars, first);
-    }
-}
-
-static GListPtr
-get_envvars_from_cib(xmlNode *basenode, crm_alert_entry_t *entry, int *count)
+get_envvars_from_cib(xmlNode *basenode, crm_alert_entry_t *entry)
 {
     xmlNode *child;
 
-    if (basenode == NULL) {
-        return entry->envvars;
+    if ((basenode == NULL) || (entry == NULL)) {
+        return;
     }
 
     child = first_named_child(basenode, XML_TAG_ATTR_SETS);
     if (child == NULL) {
-        return entry->envvars;
+        return;
+    }
+
+    if (entry->envvars == NULL) {
+        entry->envvars = crm_str_table_new();
     }
 
     for (child = first_named_child(child, XML_CIB_TAG_NVPAIR); child != NULL;
          child = __xml_next(child)) {
 
-        crm_alert_envvar_t envvar_entry = (crm_alert_envvar_t) {
-            .name = (char *) crm_element_value(child, XML_NVPAIR_ATTR_NAME),
-            .value = (char *) crm_element_value(child, XML_NVPAIR_ATTR_VALUE)
-        };
-        crm_trace("Found environment variable %s = '%s'", envvar_entry.name,
-                  (envvar_entry.value? envvar_entry.value : ""));
-        (*count)++;
-        entry->envvars = g_list_prepend(entry->envvars,
-                                        crm_dup_alert_envvar(&envvar_entry));
+        const char *name = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
+        const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
+
+        if (value == NULL) {
+            value = "";
+        }
+        g_hash_table_insert(entry->envvars, strdup(name), strdup(value));
+        crm_trace("Alert %s: added environment variable %s='%s'",
+                  entry->id, name, value);
     }
-    return entry->envvars;
 }
 
 /*!
  * \internal
  * \brief Unpack a CIB alerts section
  *
  * \param[in] alerts  XML of alerts section
  *
  * \return  List of unpacked alert entries
  *
  * \note Unlike most unpack functions, this is not used by the pengine itself,
  *       but is supplied for use by daemons that need to send alerts.
  */
 GListPtr
 pe_unpack_alerts(xmlNode *alerts)
 {
     xmlNode *alert;
-    crm_alert_entry_t entry;
+    crm_alert_entry_t *entry;
     guint max_timeout = 0;
     GListPtr alert_list = NULL;
 
     crm_alert_max_alert_timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
 
     if (alerts) {
 #ifdef RHEL7_COMPAT
         if (notify_script) {
             crm_warn("Ignoring deprecated notification configuration because alerts available");
         }
 #endif
     } else {
 #ifdef RHEL7_COMPAT
         if (notify_script) {
-            entry = (crm_alert_entry_t) {
-                .id = (char *) "legacy_notification",
-                .path = notify_script,
-                .timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS,
-                .recipient = notify_target,
-                .flags = crm_alert_default,
-                .select_attribute_name = NULL
-            };
-            alert_list = g_list_prepend(alert_list,
-                                        crm_dup_alert_entry(&entry));
+            entry = crm_alert_entry_new("legacy_notification", notify_script);
+            entry->recipient = strdup(notify_target);
+            entry->tstamp_format = strdup(CRM_ALERT_DEFAULT_TSTAMP_FORMAT);
+            alert_list = g_list_prepend(alert_list, entry);
             crm_warn("Deprecated notification syntax in use (alerts syntax is preferable)");
         }
 #endif
         return alert_list;
     }
 
     for (alert = first_named_child(alerts, XML_CIB_TAG_ALERT);
          alert; alert = __xml_next(alert)) {
 
         xmlNode *recipient;
-        int recipients = 0, envvars = 0;
-        GHashTable *config_hash = NULL;
+        int recipients = 0;
 
-        entry = (crm_alert_entry_t) {
-            .id = (char *) crm_element_value(alert, XML_ATTR_ID),
-            .path = (char *) crm_element_value(alert, XML_ALERT_ATTR_PATH),
-            .timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS,
-            .tstamp_format = (char *) CRM_ALERT_DEFAULT_TSTAMP_FORMAT,
-            .flags = crm_alert_default,
-            .select_attribute_name = NULL
-        };
+        entry = crm_alert_entry_new(crm_element_value(alert, XML_ATTR_ID),
+                                    crm_element_value(alert, XML_ALERT_ATTR_PATH));
 
-        get_envvars_from_cib(alert, &entry, &envvars);
-        config_hash = get_meta_attrs_from_cib(alert, &entry, &max_timeout);
+        get_envvars_from_cib(alert, entry);
+        get_meta_attrs_from_cib(alert, entry, &max_timeout);
+        if (entry->tstamp_format == NULL) {
+            entry->tstamp_format = strdup(CRM_ALERT_DEFAULT_TSTAMP_FORMAT);
+        }
 
-        crm_debug("Alert %s: path=%s timeout=%dms tstamp-format='%s' %d vars",
-                  entry.id, entry.path, entry.timeout, entry.tstamp_format,
-                  envvars);
+        crm_debug("Alert %s: path=%s timeout=%dms tstamp-format='%s' %u vars",
+                  entry->id, entry->path, entry->timeout, entry->tstamp_format,
+                  (entry->envvars? g_hash_table_size(entry->envvars) : 0));
 
         for (recipient = first_named_child(alert, XML_CIB_TAG_ALERT_RECIPIENT);
              recipient != NULL; recipient = __xml_next(recipient)) {
 
-            int envvars_added = 0;
+            crm_alert_entry_t *recipient_entry = crm_dup_alert_entry(entry);
 
-            entry.recipient = (char *) crm_element_value(recipient,
-                                                         XML_ALERT_ATTR_REC_VALUE);
             recipients++;
-
-            get_envvars_from_cib(recipient, &entry, &envvars_added);
-
-            {
-                crm_alert_entry_t recipient_entry = entry;
-                GHashTable *config_hash = get_meta_attrs_from_cib(recipient,
-                                                                  &recipient_entry,
-                                                                  &max_timeout);
-
-                alert_list = g_list_prepend(alert_list,
-                                            crm_dup_alert_entry(&recipient_entry));
-                crm_debug("Alert has recipient: id=%s, value=%s, "
-                          "%d additional environment variables",
-                          crm_element_value(recipient, XML_ATTR_ID),
-                          recipient_entry.recipient, envvars_added);
-                g_hash_table_destroy(config_hash);
-            }
-
-            drop_envvars(&entry, envvars_added);
+            recipient_entry->recipient = strdup(crm_element_value(recipient,
+                                                XML_ALERT_ATTR_REC_VALUE));
+            get_envvars_from_cib(recipient, recipient_entry);
+            get_meta_attrs_from_cib(recipient, recipient_entry, &max_timeout);
+
+            alert_list = g_list_prepend(alert_list, recipient_entry);
+            crm_debug("Alert %s has recipient %s with value %s and %d envvars",
+                      entry->id, ID(recipient), recipient_entry->recipient,
+                      (recipient_entry->envvars?
+                       g_hash_table_size(recipient_entry->envvars) : 0));
         }
 
         if (recipients == 0) {
-            alert_list = g_list_prepend(alert_list,
-                                        crm_dup_alert_entry(&entry));
+            alert_list = g_list_prepend(alert_list, entry);
+        } else {
+            crm_free_alert_entry(entry);
         }
-
-        drop_envvars(&entry, -1);
-        g_hash_table_destroy(config_hash);
     }
 
     if (max_timeout > 0) {
         crm_alert_max_alert_timeout = max_timeout;
     }
     return alert_list;
 }
 
 /*!
  * \internal
  * \brief Free an alert list generated by pe_unpack_alerts()
  *
  * \param[in] alert_list  Alert list to free
  */
 void
 pe_free_alert_list(GListPtr alert_list)
 {
     if (alert_list) {
         g_list_free_full(alert_list, (GDestroyNotify) crm_free_alert_entry);
     }
 }