diff --git a/management/daemon.py b/management/daemon.py
index b7bf2a66..56888fa2 100755
--- a/management/daemon.py
+++ b/management/daemon.py
@@ -203,6 +203,7 @@ def mail_aliases():
 def mail_aliases_add():
 	return add_mail_alias(
 		request.form.get('address', ''),
+		request.form.get('description', ''),
 		request.form.get('forwards_to', ''),
 		request.form.get('permitted_senders', ''),
 		env,
diff --git a/management/mailconfig.py b/management/mailconfig.py
index 7b2d68ed..1e6cd79f 100755
--- a/management/mailconfig.py
+++ b/management/mailconfig.py
@@ -312,7 +312,8 @@ def get_mail_aliases(env, as_map=False):
 	#    { dn: {string},
 	#      mail: {string}
 	#      forward_tos: {array of string},
-	#      permited_senders: {array of string}
+	#      permited_senders: {array of string},
+	#      description: {string}
 	#    }
 	#
 	c = open_database(env)
@@ -323,7 +324,7 @@ def get_mail_aliases(env, as_map=False):
 	permitted_senders = { rec["mail"][0].lower(): rec["member"] for rec in pager }
 
 	# get all alias groups
-	pager = c.paged_search(env.LDAP_ALIASES_BASE, "(objectClass=mailGroup)", attributes=['mail','member','rfc822MailMember'])
+	pager = c.paged_search(env.LDAP_ALIASES_BASE, "(objectClass=mailGroup)", attributes=['mail','member','rfc822MailMember', 'description'])
 	
 	# make a dict of aliases
 	# key=email(lowercase), value=(email, forward-tos, permitted-senders).
@@ -352,7 +353,8 @@ def get_mail_aliases(env, as_map=False):
 			"dn": alias["dn"],
 			"mail": alias_email,
 			"forward_tos": forward_tos,
-			"permitted_senders": allowed_senders
+			"permitted_senders": allowed_senders,
+			"description": alias["description"][0]
 		}
 
 	if not as_map:
@@ -382,7 +384,8 @@ def get_mail_aliases_ex(env):
 	#         address_display: "name@domain.tld", # full Unicode
 	#         forwards_to: ["user1@domain.com", "receiver-only1@domain.com", ...],
 	#         permitted_senders: ["user1@domain.com", "sender-only1@domain.com", ...] OR null,
-	#         required: True|False
+	#         required: True|False,
+	#         description: ""
 	#       },
 	#       ...
 	#     ]
@@ -390,27 +393,38 @@ def get_mail_aliases_ex(env):
 	#   ...
 	# ]
 
+	aliases=get_mail_aliases(env, as_map=True)
 	required_aliases = get_required_aliases(env)
 	domains = {}
-	for address, forwards_to, permitted_senders in get_mail_aliases(env):
+	
+	for alias_lc in aliases:
+		alias=aliases[alias_lc]
+		address=alias_lc
+		
 		# get alias info
+		forwards_to=alias["forward_tos"]
+		permitted_senders=alias["permitted_senders"]
+		description=alias["description"]
 		domain = get_domain(address)
 		required = (address in required_aliases)
-
+		
 		# add to list
 		if not domain in domains:
 			domains[domain] = {
 				"domain": domain,
 				"aliases": [],
 			}
+
 		domains[domain]["aliases"].append({
 			"address": address,
 			"address_display": prettify_idn_email_address(address),
-			"forwards_to": [prettify_idn_email_address(r.strip()) for r in forwards_to.split(",")],
-			"permitted_senders": [prettify_idn_email_address(s.strip()) for s in permitted_senders.split(",")] if permitted_senders is not None else None,
+			"forwards_to": [prettify_idn_email_address(r.strip()) for r in forwards_to],
+			"permitted_senders": [prettify_idn_email_address(s.strip()) for s in permitted_senders] if permitted_senders is not None and len(permitted_senders)>0 else None,
 			"required": required,
+			"description": description
 		})
 
+
 	# Sort domains.
 	domains = [domains[domain] for domain in utils.sort_domains(domains.keys(), env)]
 
@@ -857,10 +871,11 @@ def convert_rfc822MailMember(env, conn, dn, mail):
 			pass
 
 		
-def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exists=False, do_kick=True):
+def add_mail_alias(address, description, forwards_to, permitted_senders, env, update_if_exists=False, do_kick=True):
 	# Add a new alias group with permitted senders.
 	#
 	# address: the email address of the alias
+	# description: a text description of the alias
 	# forwards_to: a string of newline and comma separated email address
 	# where mail is delivered
 	# permitted_senders: a string of newline and comma separated email addresses of local users that are permitted to MAIL FROM the alias.
@@ -958,20 +973,30 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist
 	# save to db
 
 	conn = open_database(env)
-	existing_alias, existing_permitted_senders = find_mail_alias(env, address, ['member','rfc822MailMember'], conn)
+	existing_alias, existing_permitted_senders = find_mail_alias(env, address, ['member','rfc822MailMember', 'description'], conn)
 	if existing_alias and not update_if_exists:
 		return ("Alias already exists (%s)." % address, 400)
 	
 	cn="%s" % uuid.uuid4()
 	dn="cn=%s,%s" % (cn, env.LDAP_ALIASES_BASE)
-	if address.startswith('@') and \
-	   len(validated_forwards_to)==1 and \
-	   validated_forwards_to[0].startswith('@'):
-		description = "Domain alias %s->%s" % (address, validated_forwards_to[0])
-	elif address.startswith('@'):
-		description = "Catch-all for %s" % address
-	else:
-		description ="Mail group %s" % address
+	if not description:
+		# supply a default description for new entries that did not
+		# specify one
+		if not existing_alias:
+			if address.startswith('@') and \
+			   len(validated_forwards_to)==1 and \
+			   validated_forwards_to[0].startswith('@'):
+				description = "Domain alias %s->%s" % (address, validated_forwards_to[0])
+			elif address.startswith('@'):
+				description = "Catch-all for %s" % address
+			else:
+				description ="Mail alias %s" % address
+		
+		# when updating, ensure the description has a value because
+		# the ldap schema does not allow an empty field
+		else:
+			description=" "
+	
 	attrs = {
 		"mail": address,
 		"description": description,
@@ -980,7 +1005,7 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist
 	}
 	
 	op = conn.add_or_modify(dn, existing_alias,
-			['member', 'rfc822MailMember' ],
+			['member', 'rfc822MailMember', 'description' ],
 			['mailGroup'],
 			attrs)
 	if op == 'modify':
@@ -1104,7 +1129,7 @@ def kick(env, mail_result=None):
 		# Doesn't exist.
 		administrator = get_system_administrator(env)
 		if address == administrator: return # don't make an alias from the administrator to itself --- this alias must be created manually
-		add_mail_alias(address, administrator, "", env, do_kick=False)
+		add_mail_alias(address, "Required alias", administrator, "", env, do_kick=False)
 		if administrator not in existing_aliases: return # don't report the alias in output if the administrator alias isn't in yet -- this is a hack to supress confusing output on initial setup
 		results.append("added alias %s (=> %s)\n" % (address, administrator))
 
diff --git a/management/templates/aliases.html b/management/templates/aliases.html
index 848fcf49..52711850 100644
--- a/management/templates/aliases.html
+++ b/management/templates/aliases.html
@@ -35,6 +35,15 @@
       </div>
     </div>
   </div>
+  <div class="form-group">
+    <label for="addaliasDescription" class="col-sm-1 control-label">Comment</label>
+    <div class="col-sm-10">
+      <input type="text" class="form-control" id="addaliasDescription">
+      <div style="margin-top: 3px; padding-left: 3px; font-size: 90%" class="text-muted">
+        An optional description of the alias
+      </div>
+    </div>
+  </div>
   <div class="form-group">
     <label for="addaliasForwardsTo" class="col-sm-1 control-label">Forwards To</label>
     <div class="col-sm-10">
@@ -81,6 +90,7 @@
       <th>Alias<br></th>
       <th>Forwards To</th>
       <th>Permitted Senders</th>
+      <th>Comment</th>
     </tr>
   </thead>
   <tbody>
@@ -103,6 +113,7 @@
     <td class='address'> </td>
     <td class='forwardsTo'> </td>
     <td class='senders'> </td>
+    <td class='description'> </td>
   </tr>
   </table>
 </div>
@@ -170,6 +181,7 @@ function show_aliases() {
             n.find('td.forwardsTo').append($("<div></div>").text(alias.forwards_to[j]))
           for (var j = 0; j < (alias.permitted_senders ? alias.permitted_senders.length : 0); j++)
             n.find('td.senders').append($("<div></div>").text(alias.permitted_senders[j]))
+          n.find('td.description').append($("<div></div>").text(alias.description));
           $('#alias_table tbody').append(n);
         }
       }
@@ -208,6 +220,7 @@ var is_alias_add_update = false;
 function do_add_alias() {
   var title = (!is_alias_add_update) ? "Add Alias" : "Update Alias";
   var form_address = $("#addaliasAddress").val();
+  var form_description = $("#addaliasDescription").val();
   var form_forwardsto = $("#addaliasForwardsTo").val();
   var form_senders = ($('#addaliasForwardsToAdvanced').prop('checked') ? $("#addaliasSenders").val() : '');
   if ($('#addaliasForwardsToAdvanced').prop('checked') && !/\S/.exec($("#addaliasSenders").val())) {
@@ -220,6 +233,7 @@ function do_add_alias() {
     {
       update_if_exists: is_alias_add_update ? '1' : '0',
       address: form_address,
+      description: form_description,
       forwards_to: form_forwardsto,
       permitted_senders: form_senders
     },
@@ -237,9 +251,10 @@ function do_add_alias() {
 
 function aliases_reset_form() {
   $("#addaliasAddress").prop('disabled', false);
-  $("#addaliasAddress").val('')
-  $("#addaliasForwardsTo").val('')
-  $("#addaliasSenders").val('')
+  $("#addaliasAddress").val('');
+  $("#addaliasDescription").val('');
+  $("#addaliasForwardsTo").val('');
+  $("#addaliasSenders").val('');
   $('#alias-cancel').addClass('hidden');
   $('#add-alias-button').text('Add Alias');
   is_alias_add_update = false;
@@ -247,6 +262,7 @@ function aliases_reset_form() {
 
 function aliases_edit(elem) {
   var address = $(elem).parents('tr').attr('data-address');
+  var description = $(elem).parents('tr').find('.description div').text().trim();
   var receiverdivs = $(elem).parents('tr').find('.forwardsTo div');
   var senderdivs = $(elem).parents('tr').find('.senders div');
   var forwardsTo = "";
@@ -264,6 +280,7 @@ function aliases_edit(elem) {
   $('#alias-cancel').removeClass('hidden');
   $("#addaliasAddress").prop('disabled', true);
   $("#addaliasAddress").val(address);
+  $("#addaliasDescription").val(description);
   $("#addaliasForwardsTo").val(forwardsTo);
   $('#addaliasForwardsToAdvanced').prop('checked', senders != "");
   $('#addaliasForwardsToNotAdvanced').prop('checked', senders == "");
diff --git a/setup/migrate.py b/setup/migrate.py
index a3912001..dae96799 100755
--- a/setup/migrate.py
+++ b/setup/migrate.py
@@ -234,7 +234,7 @@ def migration_13(env):
 	
 	# 4. perform the migration
 	users=m13.create_users(env, conn, ldap, ldap_base, ldap_users_base, ldap_domains_base)
-	aliases=m13.create_aliases(conn, ldap, ldap_aliases_base)
+	aliases=m13.create_aliases(env, conn, ldap, ldap_aliases_base)
 	permitted=m13.create_permitted_senders(conn, ldap, ldap_users_base, ldap_permitted_senders_base)
 	m13.populate_aliases(conn, ldap, users, aliases)
 		
diff --git a/setup/migration_13.py b/setup/migration_13.py
index 81cdd9b4..303ba68d 100644
--- a/setup/migration_13.py
+++ b/setup/migration_13.py
@@ -107,7 +107,7 @@ def create_users(env, conn, ldapconn, ldap_base, ldap_users_base, ldap_domains_b
 	return users
 
 
-def create_aliases(conn, ldapconn, aliases_base):
+def create_aliases(env, conn, ldapconn, aliases_base):
 	# iterate through sqlite 'aliases' table and create ldap
 	# aliases but without members.  returns a map of alias->dn
 	aliases={}
@@ -122,10 +122,19 @@ def create_aliases(conn, ldapconn, aliases_base):
 		else:
 			cn="%s" % uuid.uuid4()
 			dn="cn=%s,%s" % (cn, aliases_base)
-			print("adding alias %s" % alias)
+			description="Mail group %s" % alias
+			
+			if alias.startswith("postmaster@") or \
+			   alias.startswith("hostmaster@") or \
+			   alias.startswith("abuse@") or \
+			   alias.startswith("admin@") or \
+			   alias == "administrator@" + env['PRIMARY_HOSTNAME']:
+				description = "Required alias"
+
+			print("adding alias %s" % alias)			
 			ldapconn.add(dn, ['mailGroup'], {
 				"mail": alias,
-				"description": "Mail group %s" % alias
+				"description": description
 			})
 			aliases[alias] = dn
 	return aliases
diff --git a/tests/lib/installed-state.sh b/tests/lib/installed-state.sh
index 3e3eacf0..84e81bc6 100644
--- a/tests/lib/installed-state.sh
+++ b/tests/lib/installed-state.sh
@@ -43,7 +43,8 @@ installed_state_capture() {
         echo "Unable to get aliases: rc=$? err=$REST_ERROR" 1>&2
         return 2
     fi
-    echo "$REST_OUTPUT" > "$state_dir/aliases.json"
+    # ignore/exclude the alias description field
+    echo "$REST_OUTPUT" | grep -v '"description":' > "$state_dir/aliases.json"
 
     # record dns config
     H2 "record dns details"
@@ -84,7 +85,7 @@ installed_state_compare() {
     H2 "Aliases"
     output="$(diff "$s1/aliases.json" "$s2/aliases.json" 2>&1)"
     if [ $? -ne 0 ]; then
-        change="true"
+        changed="true"
         echo "ALIASES ARE DIFFERENT!"
         echo "$output"
     else
diff --git a/tests/suites/_init.sh b/tests/suites/_init.sh
index 5466ad3b..25d8afe1 100644
--- a/tests/suites/_init.sh
+++ b/tests/suites/_init.sh
@@ -40,6 +40,7 @@ suite_start() {
 	let SUITE_COUNT_SKIPPED=0
 	let SUITE_COUNT_TOTAL=0
 	SUITE_NAME="$1"
+	SUITE_START=$(date +%s)
 	OUTDIR="$BASE_OUTPUTDIR/$SUITE_NAME"
 	mkdir -p "$OUTDIR"
 	echo ""
@@ -50,7 +51,8 @@ suite_start() {
 
 suite_end() {
 	suite_cleanup "$@"
-	echo "Suite $SUITE_NAME finished"
+	local SUITE_END=$(date +%s)
+	echo "Suite $SUITE_NAME finished ($(elapsed_pretty $SUITE_START $SUITE_END))"
 	let OVERALL_SUCCESSES+=$SUITE_COUNT_SUCCESS
 	let OVERALL_FAILURES+=$SUITE_COUNT_FAILURE
 	let OVERALL_SKIPPED+=$SUITE_COUNT_SKIPPED