diff --git a/TODO b/TODO
index c526512f9ec9b8f00c9a3f8991c85dab6337b504..807c5b9ffa38ca9d065420d40a98062f54940c2c 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,6 @@
 Current:
 
 * Implement enotify extension:
-	- Implement valid_notify_method test (currently skeleton)
 	- Implement notify_method_capability test (currently skeleton)
 	- Implement parsing :options argument
 	- Check whether handling of error conditions matches the standard
diff --git a/src/lib-sieve/plugins/enotify/ext-enotify-common.c b/src/lib-sieve/plugins/enotify/ext-enotify-common.c
index c735046cacb83f5059117603caafbf91bf83f31f..c9756a333e670f1e2a0372e40f11da32d9014708 100644
--- a/src/lib-sieve/plugins/enotify/ext-enotify-common.c
+++ b/src/lib-sieve/plugins/enotify/ext-enotify-common.c
@@ -201,9 +201,9 @@ const struct sieve_enotify_method *ext_enotify_runtime_check_operands
 (const struct sieve_runtime_env *renv, unsigned int source_line,
 	string_t *method_uri, string_t *message, string_t *from, void **context)
 {
+	const struct sieve_enotify_method *method;
 	const char *uri = str_c(method_uri);
 	const char *scheme;
-	const struct sieve_enotify_method *method;
 	
 	if ( (scheme=ext_enotify_uri_scheme_parse(&uri)) == NULL ) {
 		sieve_runtime_error
@@ -219,7 +219,7 @@ const struct sieve_enotify_method *ext_enotify_runtime_check_operands
 				"invalid notify method '%s'", scheme);
 		return NULL;
 	}
-
+	
 	if ( method->runtime_check_operands != NULL ) {
 		struct sieve_enotify_log nlog;
 		
@@ -239,6 +239,39 @@ const struct sieve_enotify_method *ext_enotify_runtime_check_operands
 	return method;
 }
 
+bool ext_enotify_runtime_method_validate
+(const struct sieve_runtime_env *renv, unsigned int source_line,
+	string_t *method_uri)
+{
+	const struct sieve_enotify_method *method;
+	const char *uri = str_c(method_uri);
+	const char *scheme;
+	
+	if ( (scheme=ext_enotify_uri_scheme_parse(&uri)) == NULL )
+		return FALSE;
+	
+	if ( (method=ext_enotify_method_find(scheme)) == NULL )
+		return FALSE;
+	
+	if ( method->runtime_check_operands != NULL ) {
+		struct sieve_enotify_log nlog;
+		
+		memset(&nlog, 0, sizeof(nlog));
+		nlog.location = sieve_error_script_location(renv->script, source_line);
+		nlog.ehandler = sieve_interpreter_get_error_handler(renv->interp);
+		nlog.prefix = NULL;
+
+		if ( method->runtime_check_operands
+			(&nlog, str_c(method_uri), uri, NULL, NULL, NULL, NULL) )
+			return TRUE;
+		
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
 /*
  * Method logging
  */
@@ -249,6 +282,8 @@ void sieve_enotify_error
 	va_list args;
 	va_start(args, fmt);
 	
+	if ( nlog == NULL ) return;
+	
 	T_BEGIN {
 		if ( nlog->prefix == NULL )
 			sieve_verror(nlog->ehandler, nlog->location, fmt, args);
@@ -266,6 +301,8 @@ void sieve_enotify_warning
 	va_list args;
 	va_start(args, fmt);
 	
+	if ( nlog == NULL ) return;
+	
 	T_BEGIN { 
 		if ( nlog->prefix == NULL )
 			sieve_vwarning(nlog->ehandler, nlog->location, fmt, args);
@@ -282,6 +319,8 @@ void sieve_enotify_log
 {
 	va_list args;
 	va_start(args, fmt);
+
+	if ( nlog == NULL ) return;
 	
 	T_BEGIN { 
 		if ( nlog->prefix == NULL )
diff --git a/src/lib-sieve/plugins/enotify/ext-enotify-common.h b/src/lib-sieve/plugins/enotify/ext-enotify-common.h
index ed372f91a46e0b1dee43fdccec9deec9aafe840e..95c2bf0aa85cc0ddbf7e9d45c1045bcf73176511 100644
--- a/src/lib-sieve/plugins/enotify/ext-enotify-common.h
+++ b/src/lib-sieve/plugins/enotify/ext-enotify-common.h
@@ -83,6 +83,10 @@ const struct sieve_enotify_method *ext_enotify_runtime_check_operands
 		string_t *method_uri, string_t *message, string_t *from, 
 		void **context);
 
+bool ext_enotify_runtime_method_validate
+	(const struct sieve_runtime_env *renv, unsigned int source_line,
+		string_t *method_uri);
+
 /*
  * Method logging
  */ 
diff --git a/src/lib-sieve/plugins/enotify/ntfy-mailto.c b/src/lib-sieve/plugins/enotify/ntfy-mailto.c
index 4a3d11c0c674bd6491c96c7b6c1f6bf69627dd55..f83732726166bd50f7a83ed32878e5a50d28c8c3 100644
--- a/src/lib-sieve/plugins/enotify/ntfy-mailto.c
+++ b/src/lib-sieve/plugins/enotify/ntfy-mailto.c
@@ -84,6 +84,7 @@ struct ntfy_mailto_context {
 	ARRAY_TYPE(headers) headers;
 	const char *subject;
 	const char *body;
+	const char *from_normalized;
 };
 
 /*
@@ -518,24 +519,47 @@ static bool ntfy_mailto_compile_check_from
  */
  
 static bool ntfy_mailto_runtime_check_operands
-	(const struct sieve_enotify_log *nlog, const char *uri,
-		const char *uri_body, string_t *message, string_t *from, 
+	(const struct sieve_enotify_log *nlog, const char *uri ATTR_UNUSED,
+		const char *uri_body, string_t *message ATTR_UNUSED, string_t *from, 
 		pool_t context_pool, void **context)
 {
 	struct ntfy_mailto_context *mtctx;
+	const char *error, *normalized;
+
+	if ( context_pool != NULL && context != NULL ) {
+		/* Need to create context before validation to have arrays present */
+		mtctx = p_new(context_pool, struct ntfy_mailto_context, 1);
 	
-	/* Need to create context before validation to have arrays present */
-	mtctx = p_new(context_pool, struct ntfy_mailto_context, 1);
-	p_array_init(&mtctx->recipients, context_pool, NTFY_MAILTO_MAX_RECIPIENTS);
-	p_array_init(&mtctx->headers, context_pool, NTFY_MAILTO_MAX_HEADERS);
-
-	if ( !ntfy_mailto_parse_uri
-		(nlog, uri_body, &mtctx->recipients, &mtctx->headers, &mtctx->body, 
-			&mtctx->subject) ) {
-		return FALSE;
-	}
+		/* Validate :from */
+		if ( from != NULL ) {
+			T_BEGIN {
+				normalized = sieve_address_normalize(from, &error);
+
+				if ( normalized == NULL ) {
+					sieve_enotify_error(nlog,
+						"specified :from address '%s' is invalid for "
+						"the mailto method: %s",
+						str_sanitize(str_c(from), 128), error);
+				} else 
+					mtctx->from_normalized = p_strdup(context_pool, normalized);
+			} T_END;
+	
+			if ( !normalized ) return FALSE;
+		}
+
+		p_array_init(&mtctx->recipients, context_pool, NTFY_MAILTO_MAX_RECIPIENTS);
+		p_array_init(&mtctx->headers, context_pool, NTFY_MAILTO_MAX_HEADERS);
+
+		if ( !ntfy_mailto_parse_uri
+			(nlog, uri_body, &mtctx->recipients, &mtctx->headers, &mtctx->body, 
+				&mtctx->subject) ) {
+			return FALSE;
+		}
 	
-	*context = (void *) mtctx;
+		*context = (void *) mtctx;
+	} else {
+		return ntfy_mailto_parse_uri(NULL, uri_body, NULL, NULL, NULL, NULL);
+	}
 
 	return TRUE;	
 }
diff --git a/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c b/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
index 2152ca03769a780c8a756e65395b56760c2f8472..847ffbb92735fb5abbe7383caa03c7726c58ab08 100644
--- a/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
+++ b/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
@@ -111,6 +111,8 @@ static int tst_vnotifym_operation_execute
 	const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
 	struct sieve_coded_stringlist *notify_uris;
+	string_t *uri_item;
+	bool result = TRUE, all_valid = TRUE;
 
 	/*
 	 * Read operands 
@@ -128,6 +130,21 @@ static int tst_vnotifym_operation_execute
 
 	sieve_runtime_trace(renv, "VALID_NOTIFY_METHOD test");
 
-	sieve_interpreter_set_test_result(renv->interp, FALSE);
+	uri_item = NULL;
+	while ( (result=sieve_coded_stringlist_next_item(notify_uris, &uri_item)) 
+		&& uri_item != NULL ) {
+		
+		if ( !ext_enotify_runtime_method_validate(renv, 0 /* FIXME */, uri_item) ) {
+			all_valid = FALSE;
+			break;
+		}
+	}
+	
+	if ( !result ) {
+		sieve_runtime_trace_error(renv, "invalid method uri item");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+	
+	sieve_interpreter_set_test_result(renv->interp, all_valid);
 	return SIEVE_EXEC_OK;
 }
diff --git a/tests/extensions/enotify/errors.svtest b/tests/extensions/enotify/errors.svtest
index cb7d4e3d65a95b4cef8a127c6212083e430b8ba0..da11ff662c2a97021790ac7aae9fe51758c1227f 100644
--- a/tests/extensions/enotify/errors.svtest
+++ b/tests/extensions/enotify/errors.svtest
@@ -6,7 +6,7 @@ require "enotify";
 
 
 test "Invalid URL (FIXME: count only)" {
-	if test_compile "errors/url.sieve" {
+	if test_compile "errors/uri.sieve" {
 		test_fail "compile should have failed";
 	}
 
@@ -16,7 +16,7 @@ test "Invalid URL (FIXME: count only)" {
 }
 
 test "Invalid mailto URL (FIXME: count only)" {
-	if test_compile "errors/url-mailto.sieve" {
+	if test_compile "errors/uri-mailto.sieve" {
 		test_fail "compile should have failed";
 	}
 
diff --git a/tests/extensions/enotify/errors/url-mailto.sieve b/tests/extensions/enotify/errors/uri-mailto.sieve
similarity index 100%
rename from tests/extensions/enotify/errors/url-mailto.sieve
rename to tests/extensions/enotify/errors/uri-mailto.sieve
diff --git a/tests/extensions/enotify/errors/url.sieve b/tests/extensions/enotify/errors/uri.sieve
similarity index 100%
rename from tests/extensions/enotify/errors/url.sieve
rename to tests/extensions/enotify/errors/uri.sieve
diff --git a/tests/extensions/enotify/valid-notify-method.svtest b/tests/extensions/enotify/valid-notify-method.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..6ad92495d682e16da6e78474b3b11632278bd7d1
--- /dev/null
+++ b/tests/extensions/enotify/valid-notify-method.svtest
@@ -0,0 +1,31 @@
+require "vnd.dovecot.testsuite";
+
+require "enotify";
+
+test "Invalid header name" {
+	if valid_notify_method 
+		"mailto:stephan@rename-it.nl?header:=frop" {
+		test_fail "invalid uri accepted";
+	}
+}
+
+test "Invalid recipient" {
+	if valid_notify_method 
+		"mailto:stephan%23rename-it.nl" {
+		test_fail "invalid uri accepted";
+	}
+}
+
+test "Invalid to header recipient" {
+	if valid_notify_method
+		"mailto:stephan@rename-it.nl?to=nico%23vestingbar.nl" {
+		test_fail "invalid uri accepted";
+	}
+}
+
+test "Valid URI" {
+	if not valid_notify_method
+		"mailto:stephan@rename-it.nl" {
+		test_fail "valid uri denied";
+	}
+}