diff --git a/TODO b/TODO
index d331ef9f32585feb359d4a8f2a18878081c8411f..7c76065a20c599cfccc6eac64e05990a4fd0fceb 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,5 @@
 Current:
 
-* Test new enotify extension extensively.
 * Test new multiscript support extensively. 
 
 Next (in order of descending priority/precedence):
diff --git a/src/lib-sieve/sieve-commands.h b/src/lib-sieve/sieve-commands.h
index 9ad11388484eccfc6bb08594371e3103c167f0df..9968c6b57ca113e6a58a89707eaea9525c3dab15 100644
--- a/src/lib-sieve/sieve-commands.h
+++ b/src/lib-sieve/sieve-commands.h
@@ -73,7 +73,8 @@ void sieve_arg_catenated_string_add_element
 enum sieve_command_type {
 	SCT_NONE,
 	SCT_COMMAND,
-	SCT_TEST
+	SCT_TEST,
+	SCT_HYBRID
 };
 
 struct sieve_command {
diff --git a/src/lib-sieve/sieve-validator.c b/src/lib-sieve/sieve-validator.c
index 09a67e95604f6f0fd1e05d1a2f28352198c9f93b..ffc6b8d71fa34338ac246ed6dbcdd6632cb948e8 100644
--- a/src/lib-sieve/sieve-validator.c
+++ b/src/lib-sieve/sieve-validator.c
@@ -926,6 +926,7 @@ static bool sieve_validate_command_subtests
 
 			switch ( ctype ) {
 			case SCT_TEST: /* Spurious test */
+			case SCT_HYBRID:
 				sieve_command_validate_error(valdtr, cmd, 
 					"the %s %s accepts no sub-tests, but tests are specified", 
 					cmd->command->identifier, sieve_command_type_name(cmd->command));
diff --git a/src/testsuite/cmd-test-message.c b/src/testsuite/cmd-test-message.c
index 05a4e1be7d85b92ddfd76fca3bcee9f831981d14..c269d02622a9c2665f5d7fa78f97582c1757ded4 100644
--- a/src/testsuite/cmd-test-message.c
+++ b/src/testsuite/cmd-test-message.c
@@ -30,7 +30,7 @@ static bool cmd_test_message_generate
 
 const struct sieve_command cmd_test_message = { 
 	"test_message", 
-	SCT_COMMAND, 
+	SCT_HYBRID, 
 	1, 0, FALSE, FALSE,
 	cmd_test_message_registered, 
 	NULL,
@@ -239,11 +239,14 @@ static bool cmd_test_message_generate
 	/* Emit operation */
 	sieve_operation_emit_code(cgenv->sbin, 
 		test_message_operations[ctx_data->msg_source]);
-	
+
+	/* Emit is_test flag */
+	sieve_binary_emit_byte(cgenv->sbin, ( cmd->ast_node->type == SAT_TEST ));
+	  	
  	/* Generate arguments */
 	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
 		return FALSE;
-	  
+
 	return TRUE;
 }
 
@@ -255,7 +258,13 @@ static bool cmd_test_message_smtp_operation_dump
 (const struct sieve_operation *op ATTR_UNUSED,
 	const struct sieve_dumptime_env *denv, sieve_size_t *address)
 {
-	sieve_code_dumpf(denv, "TEST_MESSAGE_SMTP:");
+	unsigned int is_test;
+
+    if ( !sieve_binary_read_byte(denv->sbin, address, &is_test) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "TEST_MESSAGE_SMTP (%s):", 
+		( is_test ? "TEST" : "COMMAND" ));
 	
 	sieve_code_descend(denv);
 	
@@ -266,7 +275,13 @@ static bool cmd_test_message_mailbox_operation_dump
 (const struct sieve_operation *op ATTR_UNUSED,
 	const struct sieve_dumptime_env *denv, sieve_size_t *address)
 {
-	sieve_code_dumpf(denv, "TEST_MESSAGE_MAILBOX:");
+	unsigned int is_test;
+
+    if ( !sieve_binary_read_byte(denv->sbin, address, &is_test) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "TEST_MESSAGE_MAILBOX (%s):",
+		( is_test ? "TEST" : "COMMAND" ));
 	
 	sieve_code_descend(denv);
 
@@ -284,16 +299,45 @@ static int cmd_test_message_smtp_operation_execute
 	const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
 	sieve_number_t msg_index;
+    unsigned int is_test = -1;
+	bool result;
+
+	/* 
+	 * Read operands 
+	 */
+
+	/* Is test */
+
+    if ( !sieve_binary_read_byte(renv->sbin, address, &is_test) ) {
+		sieve_runtime_trace_error(renv, "invalid is_test flag");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
 
 	/* Index */
+
 	if ( !sieve_opr_number_read(renv, address, &msg_index) ) {
 		sieve_runtime_trace_error(renv, "invalid index operand");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
+
+	/*
+	 * Perform operation
+	 */
 		
-	sieve_runtime_trace(renv, "TEST_MESSAGE_SMTP [%d]", msg_index);
+	sieve_runtime_trace(renv, "TEST_MESSAGE_SMTP (%s) [%d]", 
+		( is_test ? "TEST" : "COMMAND" ), msg_index);
+
+	result = testsuite_smtp_get(renv, msg_index);
 
-	return testsuite_smtp_get(renv, msg_index);
+	if ( is_test ) {
+		sieve_interpreter_set_test_result(renv->interp, result);
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( !result )
+		testsuite_test_failf("no outgoing SMTP message with index %d", msg_index);
+
+	return SIEVE_EXEC_OK;
 }
 
 static int cmd_test_message_mailbox_operation_execute
@@ -302,6 +346,17 @@ static int cmd_test_message_mailbox_operation_execute
 {
 	string_t *folder;
 	sieve_number_t msg_index;
+    unsigned int is_test = -1;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Is test */
+    if ( !sieve_binary_read_byte(renv->sbin, address, &is_test) ) {
+		sieve_runtime_trace_error(renv, "invalid is_test flag");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
 
 	/* Folder */
 	if ( !sieve_opr_string_read(renv, address, &folder) ) {
@@ -314,9 +369,13 @@ static int cmd_test_message_mailbox_operation_execute
 		sieve_runtime_trace_error(renv, "invalid index operand");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
+
+	/*
+	 * Perform operation
+	 */
 		
-	sieve_runtime_trace(renv, "TEST_MESSAGE_MAILBOX \"%s\" [%d]", 
-		str_c(folder), msg_index);
+	sieve_runtime_trace(renv, "TEST_MESSAGE_MAILBOX (%s) \"%s\" [%d]", 
+		( is_test ? "TEST" : "COMMAND" ), str_c(folder), msg_index);
 
 	/* FIXME: to be implemented */
 
diff --git a/src/testsuite/testsuite-common.c b/src/testsuite/testsuite-common.c
index 824a07960939c4e27741c4164a1cf9240b8d5e9a..b48894025f4732025031ebf1124e094b6f2e4037 100644
--- a/src/testsuite/testsuite-common.c
+++ b/src/testsuite/testsuite-common.c
@@ -113,18 +113,33 @@ void testsuite_test_start(string_t *name)
 }
 
 void testsuite_test_fail(string_t *reason)
+{
+	testsuite_test_fail_cstr(str_c(reason));
+}
+
+void testsuite_test_failf(const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	testsuite_test_fail_cstr(t_strdup_vprintf(fmt, args));
+
+	va_end(args);
+}
+
+void testsuite_test_fail_cstr(const char *reason)
 {	
 	if ( str_len(test_name) == 0 ) {
-		if ( reason == NULL || str_len(reason) == 0 )
+		if ( reason == NULL || *reason == '\0' )
 			printf("%2d: Test FAILED\n", test_index);
 		else
-			printf("%2d: Test FAILED: %s\n", test_index, str_c(reason));
+			printf("%2d: Test FAILED: %s\n", test_index, reason);
 	} else {
-		if ( reason == NULL || str_len(reason) == 0 )
+		if ( reason == NULL || *reason == '\0' )
 			printf("%2d: Test '%s' FAILED\n", test_index, str_c(test_name));
 		else
 			printf("%2d: Test '%s' FAILED: %s\n", test_index, 
-				str_c(test_name), str_c(reason));
+				str_c(test_name), reason);
 	}
 
 	str_truncate(test_name, 0);
diff --git a/src/testsuite/testsuite-common.h b/src/testsuite/testsuite-common.h
index a7f6be0efe2d4383d6932de41d9166dac124ca4b..50b18523ae660ede75844698379f3f6cb9001109 100644
--- a/src/testsuite/testsuite-common.h
+++ b/src/testsuite/testsuite-common.h
@@ -107,6 +107,9 @@ enum testsuite_operand_code {
 
 void testsuite_test_start(string_t *name);
 void testsuite_test_fail(string_t *reason);
+void testsuite_test_failf(const char *fmt, ...) ATTR_FORMAT(1, 2);
+void testsuite_test_fail_cstr(const char *reason);
+
 void testsuite_test_succeed(string_t *reason);
 
 void testsuite_testcase_fail(const char *reason);
diff --git a/src/testsuite/testsuite-smtp.c b/src/testsuite/testsuite-smtp.c
index 85307280d76bb31c550ba9e259e32b3074541966..0f26c62ba384d7f19d2b26dde57fd6055a6a1b64 100644
--- a/src/testsuite/testsuite-smtp.c
+++ b/src/testsuite/testsuite-smtp.c
@@ -110,17 +110,13 @@ bool testsuite_smtp_close(void *handle)
  * Access
  */
 
-int testsuite_smtp_get
+bool testsuite_smtp_get
 (const struct sieve_runtime_env *renv, unsigned int index)
 {
 	const struct testsuite_smtp_message *smtp_msg;
 
-	if ( index >= array_count(&testsuite_smtp_messages) ) {
-		sieve_runtime_error(renv,
-			sieve_error_script_location(renv->script, 0),
-			"no outgoing smtp message with index %d", index);
-		return SIEVE_EXEC_FAILURE;
-	}
+	if ( index >= array_count(&testsuite_smtp_messages) )
+		return FALSE;
 
 	smtp_msg = array_idx(&testsuite_smtp_messages, index);
 
@@ -128,5 +124,5 @@ int testsuite_smtp_get
 	testsuite_envelope_set_sender(smtp_msg->envelope_from);
 	testsuite_envelope_set_recipient(smtp_msg->envelope_to);
 
-	return SIEVE_EXEC_OK;
+	return TRUE;
 }
diff --git a/src/testsuite/testsuite-smtp.h b/src/testsuite/testsuite-smtp.h
index e7491e2b43c7adaa2b537c9bd1710d0dab3d40b4..f1e7f38a1d9015e779a6e6885937a3fb4621a225 100644
--- a/src/testsuite/testsuite-smtp.h
+++ b/src/testsuite/testsuite-smtp.h
@@ -20,7 +20,7 @@ bool testsuite_smtp_close(void *handle);
  * Access
  */
 
-int testsuite_smtp_get
+bool testsuite_smtp_get
 	(const struct sieve_runtime_env *renv, unsigned int index);
 
 #endif /* __TESTSUITE_SMTP_H */
diff --git a/tests/extensions/enotify/mailto.svtest b/tests/extensions/enotify/mailto.svtest
index 5202746b4fabcb33e82f6ce4a25297cc721f34a7..6abd107adf9f55643e7ee366d122c65a4b8d4722 100644
--- a/tests/extensions/enotify/mailto.svtest
+++ b/tests/extensions/enotify/mailto.svtest
@@ -95,5 +95,68 @@ test "Multiple recipients" {
 	}
 }
 
+/*
+ * Duplicate recipients
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
 
+test "Duplicate recipients" {
+	notify "mailto:timo@example.com%2cstephan@dovecot.org?cc=stephan@dovecot.org";
+	notify "mailto:stephan@rename-it.nl?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if address "Cc" "stephan@dovecot.org" {
+		test_fail "duplicate recipient not removed from first message";
+	}
+
+	test_message :smtp 1;
+
+	if address "Cc" "timo@example.com" {
+		test_fail "duplicate recipient not removed from second message";
+	}
+}
+
+
+/*
+ * Notifying on automated messages
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Auto-submitted: auto-notify
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Notifying on automated messages" {
+	notify "mailto:stephan@rename-it.nl?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "notified of auto-submitted message";
+	}
+}