diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index aba132bae375da55c5cb21e815aac342ede16d23..22420fae996d5c32bdeb1914b3141671381ec2ac 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -98,6 +98,7 @@ libdovecot_sieve_la_LIBADD = \
 
 libdovecot_sieve_la_SOURCES = \
 	sieve-settings.old.c \
+	sieve-settings.c \
 	sieve-message.c \
 	sieve-smtp.c \
 	sieve-lexer.c \
@@ -144,6 +145,7 @@ headers = \
 	sieve-types.h \
 	sieve-common.h \
 	sieve-limits.h \
+	sieve-settings.h \
 	sieve-settings.old.h \
 	sieve-message.h \
 	sieve-smtp.h \
diff --git a/src/lib-sieve/cmd-redirect.c b/src/lib-sieve/cmd-redirect.c
index 28a798c9e055b539880ff4f4008d7993a34b6014..1de8e988e08678b443a7f406f679ea4070d5613e 100644
--- a/src/lib-sieve/cmd-redirect.c
+++ b/src/lib-sieve/cmd-redirect.c
@@ -151,7 +151,7 @@ cmd_redirect_validate(struct sieve_validator *validator,
 		return result;
 	}
 
-	if (svinst->max_redirects == 0) {
+	if (svinst->set->max_redirects == 0) {
 		sieve_command_validate_error(validator, cmd,
 			"local policy prohibits the use of a redirect action");
 		return FALSE;
@@ -233,7 +233,7 @@ cmd_redirect_operation_execute(const struct sieve_runtime_env *renv,
 		return SIEVE_EXEC_FAILURE;
 	}
 
-	if (svinst->max_redirects == 0) {
+	if (svinst->set->max_redirects == 0) {
 		sieve_runtime_error(renv, NULL,
 			"local policy prohibits the use of a redirect action");
 		return SIEVE_EXEC_FAILURE;
@@ -309,7 +309,8 @@ act_redirect_send(const struct sieve_action_exec_env *aenv, struct mail *mail,
 	struct sieve_instance *svinst = eenv->svinst;
 	struct sieve_message_context *msgctx = aenv->msgctx;
 	const struct sieve_script_env *senv = eenv->scriptenv;
-	struct sieve_address_source env_from = svinst->redirect_from;
+	struct sieve_address_source env_from =
+		svinst->set->parsed.redirect_envelope_from;
 	struct istream *input;
 	struct ostream *output;
 	const struct smtp_address *sender;
@@ -357,7 +358,7 @@ act_redirect_send(const struct sieve_action_exec_env *aenv, struct mail *mail,
 		if (ret < 0)
 			sender = NULL;
 		else if (ret == 0)
-			sender = svinst->user_email;
+			sender = svinst->set->parsed.user_email;
 	}
 
 	/* Open SMTP transport */
@@ -376,7 +377,7 @@ act_redirect_send(const struct sieve_action_exec_env *aenv, struct mail *mail,
 		/* Prepend sieve headers (should not affect signatures) */
 		rfc2822_header_append(hdr, "X-Sieve", SIEVE_IMPLEMENTATION,
 				      FALSE, NULL);
-		if (svinst->user_email == NULL &&
+		if (svinst->set->parsed.user_email == NULL &&
 		    (eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
 			user_email = sieve_message_get_final_recipient(msgctx);
 		else
@@ -654,7 +655,7 @@ act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
 		   destination */
 		sieve_action_duplicate_mark(
 			aenv, trans->dupeid, strlen(trans->dupeid),
-			ioloop_time + svinst->redirect_duplicate_period);
+			ioloop_time + svinst->set->redirect_duplicate_period);
 
 		eenv->exec_status->significant_action_executed = TRUE;
 
diff --git a/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c b/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
index 29ff02d00ac519bab9629b2b8db80bb809d33f27..5620e28deef434821dbc47dba08962104cd343ba 100644
--- a/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
+++ b/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
@@ -575,8 +575,8 @@ ntfy_mailto_send(const struct sieve_enotify_exec_env *nenv,
 	} else if (ret == 0) {
 		if (mtactx->from_address != NULL)
 			from_smtp = mtactx->from_address;
-		else if (svinst->user_email != NULL)
-			from_smtp = svinst->user_email;
+		else if (svinst->set->parsed.user_email != NULL)
+			from_smtp = svinst->set->parsed.user_email;
 		else {
 			from_smtp = sieve_get_postmaster_smtp(senv);
 			if (from == NULL)
@@ -763,7 +763,7 @@ ntfy_mailto_action_execute(const struct sieve_enotify_exec_env *nenv,
 	const char *const *hdsp;
 	int ret;
 
-	owner_email = svinst->user_email;
+	owner_email = svinst->set->parsed.user_email;
 	if (owner_email == NULL &&
 	    (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
 		owner_email = sieve_message_get_final_recipient(nenv->msgctx);
diff --git a/src/lib-sieve/plugins/vacation/cmd-vacation.c b/src/lib-sieve/plugins/vacation/cmd-vacation.c
index 2748574e638ffa6dc59e9ccacda4b8020a05b8bc..cdda942136e01eb924ec2986f2be5da1270301c7 100644
--- a/src/lib-sieve/plugins/vacation/cmd-vacation.c
+++ b/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -1437,8 +1437,8 @@ act_vacation_commit(const struct sieve_action_exec_env *aenv,
 	if (extctx->use_original_recipient)
 		orig_recipient = sieve_message_get_orig_recipient(aenv->msgctx);
 	/* Fetch explicitly configured user email address */
-	if (svinst->user_email != NULL)
-		user_email = svinst->user_email;
+	if (svinst->set->parsed.user_email != NULL)
+		user_email = svinst->set->parsed.user_email;
 
 	/* Is the original message directly addressed to the user or the addresses
 	 * specified using the :addresses tag?
diff --git a/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c b/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
index ee20f5a5eb8d1f946871c009159e9fa7582dbb37..11a09bb05aa2959bac735c5800a24629ede493db 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
+++ b/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
@@ -566,8 +566,8 @@ act_report_send(const struct sieve_action_exec_env *aenv,
 				smtp_address_encode_path(orig_recipient));
 		}
 	}
-	if (svinst->user_email != NULL)
-		user = svinst->user_email;
+	if (svinst->set->parsed.user_email != NULL)
+		user = svinst->set->parsed.user_email;
 	else if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ||
 		 (user = sieve_message_get_orig_recipient(msgctx)) == NULL)
 		user = sieve_get_user_email(svinst);
diff --git a/src/lib-sieve/sieve-actions.c b/src/lib-sieve/sieve-actions.c
index 2b653869b3e6abd90388e989df1e21994949a85f..f7e01ff8d7aaf27b216c6ab886d36317432b8598 100644
--- a/src/lib-sieve/sieve-actions.c
+++ b/src/lib-sieve/sieve-actions.c
@@ -916,7 +916,7 @@ int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
 	act->to_address = smtp_address_clone(pool, to_address);
 
 	if (sieve_result_add_action(renv, NULL, name, &act_redirect, seffects,
-				    act, svinst->max_redirects,
+				    act, svinst->set->max_redirects,
 				    TRUE) < 0)
 		return SIEVE_EXEC_FAILURE;
 
diff --git a/src/lib-sieve/sieve-address-source.c b/src/lib-sieve/sieve-address-source.c
index 4560e5636caee068f36545451467c0e7d59ee8f9..db8979a2d89f3ea512dc83bd000bd0d540154995 100644
--- a/src/lib-sieve/sieve-address-source.c
+++ b/src/lib-sieve/sieve-address-source.c
@@ -77,7 +77,7 @@ int sieve_address_source_get_address(struct sieve_address_source *asrc,
 	enum sieve_address_source_type type = asrc->type;
 
 	if (type == SIEVE_ADDRESS_SOURCE_USER_EMAIL &&
-	    svinst->user_email == NULL)
+	    svinst->set->parsed.user_email == NULL)
 		type = SIEVE_ADDRESS_SOURCE_RECIPIENT;
 
 	if ((flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0) {
@@ -104,7 +104,7 @@ int sieve_address_source_get_address(struct sieve_address_source *asrc,
 		*addr_r = sieve_message_get_orig_recipient(msgctx);
 		return 1;
 	case SIEVE_ADDRESS_SOURCE_USER_EMAIL:
-		*addr_r = svinst->user_email;
+		*addr_r = svinst->set->parsed.user_email;
 		return 1;
 	case SIEVE_ADDRESS_SOURCE_POSTMASTER:
 		*addr_r = sieve_get_postmaster_smtp(senv);
diff --git a/src/lib-sieve/sieve-address-source.h b/src/lib-sieve/sieve-address-source.h
index b7a2f9ebfc85b88a11642e70f26d5faa0e5bc5ac..5a300ff7a404914462f2d1b826968b57d7f645a9 100644
--- a/src/lib-sieve/sieve-address-source.h
+++ b/src/lib-sieve/sieve-address-source.h
@@ -3,6 +3,8 @@
 
 #include "sieve-common.h"
 
+struct sieve_message_context;
+
 enum sieve_address_source_type {
 	SIEVE_ADDRESS_SOURCE_DEFAULT = 0,
 	SIEVE_ADDRESS_SOURCE_SENDER,
diff --git a/src/lib-sieve/sieve-binary.c b/src/lib-sieve/sieve-binary.c
index 9d620a7eb88dbb2f1ba0831b72858089d4a6adf5..774ce705951d411e814408d2d29a8b9522dbd1d1 100644
--- a/src/lib-sieve/sieve-binary.c
+++ b/src/lib-sieve/sieve-binary.c
@@ -195,7 +195,7 @@ void sieve_binary_get_resource_usage(struct sieve_binary *sbin,
 {
 	struct sieve_binary_header *header = &sbin->header;
 	time_t update_time = header->resource_usage.update_time;
-	unsigned int timeout = sbin->svinst->resource_usage_timeout_secs;
+	unsigned int timeout = sbin->svinst->set->resource_usage_timeout;
 
 	if (update_time != 0 && (ioloop_time - update_time) > (time_t)timeout)
 		i_zero(&header->resource_usage);
diff --git a/src/lib-sieve/sieve-common.h b/src/lib-sieve/sieve-common.h
index 53068dd3f45eb1145eaa28ebc1ffc9a5ef99b1df..6d1c78ad555f54ec537d4c7811f3e58a5dcdef99 100644
--- a/src/lib-sieve/sieve-common.h
+++ b/src/lib-sieve/sieve-common.h
@@ -4,6 +4,9 @@
 #include "lib.h"
 
 #include "sieve.h"
+#ifndef SETTINGS_PLUGIN
+#include "sieve-settings.h"
+#endif
 
 #include <sys/types.h>
 
@@ -162,8 +165,6 @@ extern struct event_category event_category_sieve;
  * Sieve engine instance
  */
 
-#include "sieve-address-source.h"
-
 struct sieve_instance {
 	/* Main engine pool */
 	pool_t pool;
@@ -201,14 +202,8 @@ struct sieve_instance {
 	enum sieve_delivery_phase delivery_phase;
 
 	/* Settings */
-	size_t max_script_size;
-	unsigned int max_actions;
-	unsigned int max_redirects;
-	unsigned int max_cpu_time_secs;
-	unsigned int resource_usage_timeout_secs;
-	const struct smtp_address *user_email, *user_email_implicit;
-	struct sieve_address_source redirect_from;
-	unsigned int redirect_duplicate_period;
+	const struct sieve_settings *set;
+	const struct smtp_address *user_email_implicit;
 };
 
 /*
diff --git a/src/lib-sieve/sieve-interpreter.c b/src/lib-sieve/sieve-interpreter.c
index e0bd6ee3159eeddf89755321d80338a88a6536be..67a3286ef9ff7e83f92478be3ab19236a59a53e7 100644
--- a/src/lib-sieve/sieve-interpreter.c
+++ b/src/lib-sieve/sieve-interpreter.c
@@ -935,8 +935,8 @@ int sieve_interpreter_continue(struct sieve_interpreter *interp,
 	if (interrupted != NULL)
 		*interrupted = FALSE;
 
-	if (svinst->max_cpu_time_secs > 0) {
-		climit = cpu_limit_init(svinst->max_cpu_time_secs,
+	if (svinst->set->max_cpu_time > 0) {
+		climit = cpu_limit_init(svinst->set->max_cpu_time,
 					CPU_LIMIT_TYPE_USER);
 	}
 
diff --git a/src/lib-sieve/sieve-lexer.c b/src/lib-sieve/sieve-lexer.c
index 409f5e1a54b703e6e2ea91077c405634d0cc7b7a..f916bfabec859b759f6f749c9b143a66378b8540 100644
--- a/src/lib-sieve/sieve-lexer.c
+++ b/src/lib-sieve/sieve-lexer.c
@@ -66,11 +66,11 @@ sieve_lexer_create(struct sieve_script *script,
 
 	/* Check script size */
 	if (i_stream_stat(stream, TRUE, &st) >= 0 && st->st_size > 0 &&
-	    svinst->max_script_size > 0 &&
-	    (uoff_t)st->st_size > svinst->max_script_size) {
+	    svinst->set->max_script_size > 0 &&
+	    (uoff_t)st->st_size > svinst->set->max_script_size) {
 		sieve_error(ehandler, sieve_script_name(script),
 			"sieve script is too large (max %zu bytes)",
-			svinst->max_script_size);
+			svinst->set->max_script_size);
 		if (error_code_r != NULL)
 			*error_code_r = SIEVE_ERROR_NOT_POSSIBLE;
 		return NULL;
diff --git a/src/lib-sieve/sieve-result.c b/src/lib-sieve/sieve-result.c
index 1ed4adf105ad06709ae6429d877f3bbf40f15a06..5b8da5edbd4647d15e76fb9f5875a7f1b754785a 100644
--- a/src/lib-sieve/sieve-result.c
+++ b/src/lib-sieve/sieve-result.c
@@ -537,14 +537,15 @@ _sieve_result_add_action(const struct sieve_runtime_env *renv,
 		raction = kaction;
 	} else {
 		/* Check policy limit on total number of actions */
-		if (svinst->max_actions > 0 &&
-		    result->action_count >= svinst->max_actions)
+		if (svinst->set->max_actions > 0 &&
+		    result->action_count >= svinst->set->max_actions)
 		{
 			sieve_runtime_error(
 				renv, action.location,
 				"total number of actions exceeds policy limit "
 				"(%u > %u)",
-				result->action_count+1, svinst->max_actions);
+				result->action_count+1,
+				svinst->set->max_actions);
 			return -1;
 		}
 
diff --git a/src/lib-sieve/sieve-settings.c b/src/lib-sieve/sieve-settings.c
new file mode 100644
index 0000000000000000000000000000000000000000..744cb5f0210b5779eb40490affea10eab496cb52
--- /dev/null
+++ b/src/lib-sieve/sieve-settings.c
@@ -0,0 +1,106 @@
+/* Copyright (c) 2024 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "settings.h"
+#include "settings-parser.h"
+
+#include "sieve-limits.h"
+#include "sieve-settings.h"
+
+#include <ctype.h>
+
+static bool sieve_settings_check(void *_set, pool_t pool, const char **error_r);
+
+#undef DEF
+#define DEF(type, name) SETTING_DEFINE_STRUCT_##type( \
+	SIEVE_SETTINGS_FILTER"_"#name, name, struct sieve_settings)
+
+static const struct setting_define sieve_setting_defines[] = {
+	DEF(BOOL, enabled),
+
+	DEF(SIZE, max_script_size),
+	DEF(UINT, max_actions),
+	DEF(UINT, max_redirects),
+	DEF(TIME, max_cpu_time),
+	DEF(TIME, resource_usage_timeout),
+
+	DEF(STR, redirect_envelope_from),
+	DEF(UINT, redirect_duplicate_period),
+
+	DEF(STR, user_email),
+	DEF(STR, user_log),
+
+	DEF(STR, trace_dir),
+	DEF(ENUM, trace_level),
+	DEF(BOOL, trace_debug),
+	DEF(BOOL, trace_addresses),
+
+	SETTING_DEFINE_LIST_END,
+};
+
+static const struct sieve_settings sieve_default_settings = {
+	.enabled = TRUE,
+
+	.max_script_size = SIEVE_DEFAULT_MAX_SCRIPT_SIZE,
+	.max_actions = SIEVE_DEFAULT_MAX_ACTIONS,
+	.max_redirects = SIEVE_DEFAULT_MAX_REDIRECTS,
+	.max_cpu_time = 0, /* FIXME: svinst->env_location == SIEVE_ENV_LOCATION_MS */
+
+	.resource_usage_timeout =
+		SIEVE_DEFAULT_RESOURCE_USAGE_TIMEOUT_SECS,
+	.redirect_envelope_from = "",
+	.redirect_duplicate_period = DEFAULT_REDIRECT_DUPLICATE_PERIOD,
+
+	.user_email = "",
+	.user_log = "",
+
+	.trace_dir = "",
+	.trace_level = "none:actions:commands:tests:matching",
+	.trace_debug = FALSE,
+	.trace_addresses = FALSE,
+};
+
+const struct setting_parser_info sieve_setting_parser_info = {
+	.name = "sieve",
+
+	.defines = sieve_setting_defines,
+	.defaults = &sieve_default_settings,
+
+	.struct_size = sizeof(struct sieve_settings),
+
+	.check_func = sieve_settings_check,
+
+	.pool_offset1 = 1 + offsetof(struct sieve_settings, pool),
+};
+
+/* <settings checks> */
+static bool
+sieve_settings_check(void *_set, pool_t pool, const char **error_r)
+{
+	struct sieve_settings *set = _set;
+	struct smtp_address *address = NULL;
+	const char *error;
+
+	if (!sieve_address_source_parse(
+		pool, set->redirect_envelope_from,
+		&set->parsed.redirect_envelope_from)) {
+		*error_r = t_strdup_printf("Invalid address source '%s'",
+					   set->redirect_envelope_from);
+		return FALSE;
+	}
+
+	if (*set->user_email != '\0' &&
+	    smtp_address_parse_path(pool, set->user_email,
+				    SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+				    &address, &error) < 0) {
+		*error_r = t_strdup_printf(
+			"Invalid SMTP address '%s': %s",
+			set->user_email, error);
+		return FALSE;
+	}
+	set->parsed.user_email = address;
+	return TRUE;
+}
+/* </settings checks> */
diff --git a/src/lib-sieve/sieve-settings.h b/src/lib-sieve/sieve-settings.h
new file mode 100644
index 0000000000000000000000000000000000000000..568963eac260f971b56207ff80f9557fef5ac44c
--- /dev/null
+++ b/src/lib-sieve/sieve-settings.h
@@ -0,0 +1,43 @@
+#ifndef SIEVE_SETTINGS_H
+#define SIEVE_SETTINGS_H
+
+#include "smtp-address.h"
+
+#include "sieve-config.h"
+#include "sieve-address-source.h"
+
+#define SIEVE_SETTINGS_FILTER "sieve"
+
+struct sieve_address_source;
+
+struct sieve_settings {
+	pool_t pool;
+
+	bool enabled;
+
+	size_t max_script_size;
+	unsigned int max_actions;
+	unsigned int max_redirects;
+	unsigned int max_cpu_time;
+	unsigned int resource_usage_timeout;
+
+	const char* redirect_envelope_from;
+	unsigned int redirect_duplicate_period;
+
+	const char *user_email;
+	const char *user_log;
+
+	const char *trace_dir;
+	const char *trace_level;
+	bool trace_debug;
+	bool trace_addresses;
+
+	struct {
+		struct sieve_address_source redirect_envelope_from;
+		const struct smtp_address *user_email;
+	} parsed;
+};
+
+extern const struct setting_parser_info sieve_setting_parser_info;
+
+#endif
diff --git a/src/lib-sieve/sieve-settings.old.c b/src/lib-sieve/sieve-settings.old.c
index 7068c2691f2ef9a00a0b829b094f7109ccc34362..adf2752c4b0f617cc54ef3967927e7bf6f6519e7 100644
--- a/src/lib-sieve/sieve-settings.old.c
+++ b/src/lib-sieve/sieve-settings.old.c
@@ -189,82 +189,3 @@ bool sieve_setting_get_duration_value(struct sieve_instance *svinst,
 	*value_r = (unsigned int)(value * multiply);
 	return TRUE;
 }
-
-/*
- * Main Sieve engine settings
- */
-
-void sieve_settings_load(struct sieve_instance *svinst)
-{
-	const char *str_setting, *error;
-	unsigned long long int uint_setting;
-	size_t size_setting;
-	sieve_number_t period;
-
-	svinst->max_script_size = SIEVE_DEFAULT_MAX_SCRIPT_SIZE;
-	if (sieve_setting_get_size_value(svinst, "sieve_max_script_size",
-					 &size_setting))
-		svinst->max_script_size = size_setting;
-
-	svinst->max_actions = SIEVE_DEFAULT_MAX_ACTIONS;
-	if (sieve_setting_get_uint_value(svinst, "sieve_max_actions",
-					 &uint_setting))
-		svinst->max_actions = (unsigned int)uint_setting;
-
-	svinst->max_redirects = SIEVE_DEFAULT_MAX_REDIRECTS;
-	if (sieve_setting_get_uint_value(svinst, "sieve_max_redirects",
-					 &uint_setting))
-		svinst->max_redirects = (unsigned int)uint_setting;
-
-	svinst->max_cpu_time_secs =
-		(svinst->env_location == SIEVE_ENV_LOCATION_MS ?
-		 0 : SIEVE_DEFAULT_MAX_CPU_TIME_SECS);
-	if (sieve_setting_get_duration_value(svinst, "sieve_max_cpu_time",
-					     &period)) {
-		if (period > (UINT_MAX / 1000))
-			svinst->max_cpu_time_secs = (UINT_MAX / 1000);
-		else
-			svinst->max_cpu_time_secs = (unsigned int)period;
-	}
-	svinst->resource_usage_timeout_secs =
-		SIEVE_DEFAULT_RESOURCE_USAGE_TIMEOUT_SECS;
-	if (sieve_setting_get_duration_value(
-		svinst, "sieve_resource_usage_timeout", &period)) {
-		if (period > UINT_MAX)
-			svinst->resource_usage_timeout_secs = UINT_MAX;
-		else {
-			svinst->resource_usage_timeout_secs =
-				(unsigned int)period;
-		}
-	}
-
-	(void)sieve_address_source_parse_from_setting(
-		svinst,	svinst->pool, "sieve_redirect_envelope_from",
-		&svinst->redirect_from);
-
-	svinst->redirect_duplicate_period = DEFAULT_REDIRECT_DUPLICATE_PERIOD;
-	if (sieve_setting_get_duration_value(
-		svinst, "sieve_redirect_duplicate_period", &period)) {
-		if (period > UINT_MAX)
-			svinst->redirect_duplicate_period = UINT_MAX;
-		else {
-			svinst->redirect_duplicate_period =
-				(unsigned int)period;
-		}
-	}
-
-	str_setting = sieve_setting_get(svinst, "sieve_user_email");
-	if (str_setting != NULL && *str_setting != '\0') {
-		struct smtp_address *address;
-		if (smtp_address_parse_path(
-			svinst->pool, str_setting,
-			SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
-			&address, &error) < 0) {
-			e_warning(svinst->event,
-				  "Invalid address value for setting "
-				  "'sieve_user_email': %s", error);
-		} else {
-			svinst->user_email = address;
-		}
-	}
-}
diff --git a/src/lib-sieve/sieve-settings.old.h b/src/lib-sieve/sieve-settings.old.h
index a5457ec93f8bb1bc162e15bd2edd39e7c17e2ec2..4038299ef1d1f5ecd22a8efb8e30720354bddc0d 100644
--- a/src/lib-sieve/sieve-settings.old.h
+++ b/src/lib-sieve/sieve-settings.old.h
@@ -31,12 +31,6 @@ bool sieve_setting_get_duration_value(struct sieve_instance *svinst,
 				      const char *setting,
 				      sieve_number_t *value_r);
 
-/*
- * Main Sieve engine settings
- */
-
-void sieve_settings_load(struct sieve_instance *svinst);
-
 /*
  * Home directory
  */
diff --git a/src/lib-sieve/sieve-storage.c b/src/lib-sieve/sieve-storage.c
index 462313ae6c3b62b27ad3c214505434c5234ee3a5..20d6e027f0711795b5e74d1f8737d110a999c2a4 100644
--- a/src/lib-sieve/sieve-storage.c
+++ b/src/lib-sieve/sieve-storage.c
@@ -492,7 +492,7 @@ int sieve_storage_create_personal(struct sieve_instance *svinst,
 				  enum sieve_error *error_code_r)
 {
 	struct sieve_storage *storage = NULL;
-	const char *set_enabled, *set_default, *set_default_name;
+	const char *set_default, *set_default_name;
 	enum sieve_error error_code;
 	int ret;
 
@@ -503,8 +503,7 @@ int sieve_storage_create_personal(struct sieve_instance *svinst,
 		error_code_r = &error_code;
 
 	/* Check whether Sieve is disabled for this user */
-	if ((set_enabled = sieve_setting_get(svinst, "sieve_enabled")) != NULL &&
-	    strcasecmp(set_enabled, "no") == 0) {
+	if (!svinst->set->enabled) {
 		e_debug(svinst->event,
 			"Sieve is disabled for this user");
 		*error_code_r = SIEVE_ERROR_NOT_POSSIBLE;
diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c
index ca90a3ac3d0c8ca93d25dd6a3d5947736a3d4883..f3625c1b7733b5ab82cd4a83fe313f5f472253f6 100644
--- a/src/lib-sieve/sieve.c
+++ b/src/lib-sieve/sieve.c
@@ -10,6 +10,7 @@
 #include "eacces-error.h"
 #include "home-expand.h"
 #include "hostpid.h"
+#include "settings.h"
 #include "message-address.h"
 #include "mail-user.h"
 
@@ -57,6 +58,8 @@ int sieve_init(const struct sieve_environment *env,
 {
 	struct event *event;
 	struct sieve_instance *svinst;
+	const char *error;
+	struct sieve_settings *set;
 	const char *domain;
 	pool_t pool;
 
@@ -67,6 +70,13 @@ int sieve_init(const struct sieve_environment *env,
 	event_set_forced_debug(event, debug);
 	event_set_append_log_prefix(event, "sieve: ");
 	event_add_str(event, "user", env->username);
+	event_set_ptr(event, SETTINGS_EVENT_FILTER_NAME, SIEVE_SETTINGS_FILTER);
+	if (settings_get(event, &sieve_setting_parser_info, 0,
+			 &set, &error) < 0) {
+		e_error(event, "%s", error);
+		event_unref(&event);
+		return -1;
+	}
 
 	/* Create Sieve engine instance */
 	pool = pool_alloconly_create("sieve", 8192);
@@ -83,6 +93,7 @@ int sieve_init(const struct sieve_environment *env,
 	svinst->env_location = env->location;
 	svinst->delivery_phase = env->delivery_phase;
 	svinst->event = event;
+	svinst->set = set;
 
 	/* Determine domain */
 	if (env->domainname != NULL && *(env->domainname) != '\0')
@@ -114,10 +125,6 @@ int sieve_init(const struct sieve_environment *env,
 	e_debug(event, "%s version %s initializing",
 		PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
 
-	/* Read configuration */
-
-	sieve_settings_load(svinst);
-
 	/* Initialize extensions */
 	if (sieve_extensions_init(svinst) < 0) {
 		sieve_deinit(&svinst);
@@ -150,11 +157,28 @@ void sieve_deinit(struct sieve_instance **_svinst)
 	sieve_extensions_deinit(svinst);
 	sieve_errors_deinit(svinst);
 
+	settings_free(svinst->set);
 	event_unref(&svinst->event);
 
 	pool_unref(&(svinst)->pool);
 }
 
+int sieve_settings_reload(struct sieve_instance *svinst)
+{
+	struct sieve_settings *set;
+	const char *error;
+
+	if (settings_get(svinst->event, &sieve_setting_parser_info, 0,
+			 &set, &error) < 0) {
+		e_error(svinst->event, "%s", error);
+		return -1;
+	}
+
+	settings_free(svinst->set);
+	svinst->set = set;
+	return 0;
+}
+
 void sieve_set_extensions(struct sieve_instance *svinst, const char *extensions)
 {
 	sieve_extensions_set_string(svinst, extensions, FALSE, FALSE);
@@ -974,17 +998,17 @@ int sieve_multiscript_finish(struct sieve_multiscript **_mscript,
 
 unsigned int sieve_max_redirects(struct sieve_instance *svinst)
 {
-	return svinst->max_redirects;
+	return svinst->set->max_redirects;
 }
 
 unsigned int sieve_max_actions(struct sieve_instance *svinst)
 {
-	return svinst->max_actions;
+	return svinst->set->max_actions;
 }
 
 size_t sieve_max_script_size(struct sieve_instance *svinst)
 {
-	return svinst->max_script_size;
+	return svinst->set->max_script_size;
 }
 
 /*
@@ -995,10 +1019,10 @@ const char *
 sieve_user_get_log_path(struct sieve_instance *svinst,
 			struct sieve_script *user_script)
 {
-	const char *log_path = NULL;
+	const char *log_path = (*svinst->set->user_log == '\0' ?
+				NULL : svinst->set->user_log);
 
 	/* Determine user log file path */
-	log_path = sieve_setting_get(svinst, "sieve_user_log");
 	if (log_path == NULL) {
 		const char *path;
 
@@ -1095,11 +1119,10 @@ int sieve_trace_log_create_dir(struct sieve_instance *svinst, const char *dir,
 int sieve_trace_log_open(struct sieve_instance *svinst,
 			 struct sieve_trace_log **trace_log_r)
 {
-	const char *trace_dir =
-		sieve_setting_get(svinst, "sieve_trace_dir");
+	const char *trace_dir = svinst->set->trace_dir;
 
 	*trace_log_r = NULL;
-	if (trace_dir == NULL)
+	if (*trace_dir == '\0')
 		return -1;
 
 	if (svinst->home_dir != NULL) {
@@ -1165,14 +1188,11 @@ void sieve_trace_log_free(struct sieve_trace_log **_trace_log)
 int sieve_trace_config_get(struct sieve_instance *svinst,
 			   struct sieve_trace_config *tr_config)
 {
-	const char *tr_level =
-		sieve_setting_get(svinst, "sieve_trace_level");
-	bool tr_debug, tr_addresses;
+	const char *tr_level = svinst->set->trace_level;
 
 	i_zero(tr_config);
 
-	if (tr_level == NULL || *tr_level == '\0' ||
-	    strcasecmp(tr_level, "none") == 0)
+	if (*tr_level == '\0' || strcasecmp(tr_level, "none") == 0)
 		return -1;
 
 	if (strcasecmp(tr_level, "actions") == 0)
@@ -1188,16 +1208,9 @@ int sieve_trace_config_get(struct sieve_instance *svinst,
 		return -1;
 	}
 
-	tr_debug = FALSE;
-	(void)sieve_setting_get_bool_value(svinst, "sieve_trace_debug",
-					   &tr_debug);
-	tr_addresses = FALSE;
-	(void)sieve_setting_get_bool_value(svinst, "sieve_trace_addresses",
-					   &tr_addresses);
-
-	if (tr_debug)
+	if (svinst->set->trace_debug)
 		tr_config->flags |= SIEVE_TRFLG_DEBUG;
-	if (tr_addresses)
+	if (svinst->set->trace_addresses)
 		tr_config->flags |= SIEVE_TRFLG_ADDRESSES;
 	return 0;
 }
@@ -1236,8 +1249,8 @@ const struct smtp_address *sieve_get_user_email(struct sieve_instance *svinst)
 
 	if (svinst->user_email_implicit != NULL)
 		return svinst->user_email_implicit;
-	if (svinst->user_email != NULL)
-		return svinst->user_email;
+	if (svinst->set->parsed.user_email != NULL)
+		return svinst->set->parsed.user_email;
 
 	if (smtp_address_parse_mailbox(svinst->pool, username, 0,
 				       &address, NULL) >= 0) {
@@ -1314,10 +1327,11 @@ bool sieve_resource_usage_is_excessive(
 	struct sieve_instance *svinst,
 	const struct sieve_resource_usage *rusage)
 {
-	i_assert(svinst->max_cpu_time_secs <= (UINT_MAX / 1000));
-	if (svinst->max_cpu_time_secs == 0)
+	i_assert(svinst->set->max_cpu_time <= (UINT_MAX / 1000));
+	if (svinst->set->max_cpu_time == 0)
 		return FALSE;
-	return (rusage->cpu_time_msecs > (svinst->max_cpu_time_secs * 1000));
+	return (rusage->cpu_time_msecs >
+		(svinst->set->max_cpu_time * 1000));
 }
 
 const char *
diff --git a/src/lib-sieve/sieve.h b/src/lib-sieve/sieve.h
index cd4ff7b6de80f1816c5406153dce085271a19e57..29bd320a2e7da822544bc62a88651f5b135fcd26 100644
--- a/src/lib-sieve/sieve.h
+++ b/src/lib-sieve/sieve.h
@@ -21,6 +21,9 @@ int sieve_init(const struct sieve_environment *env,
 /* Free all memory allocated by the sieve engine. */
 void sieve_deinit(struct sieve_instance **_svinst);
 
+/* Reload main engine settings */
+int sieve_settings_reload(struct sieve_instance *svinst);
+
 /* Get capability string for a particular extension. */
 const char *
 sieve_get_capabilities(struct sieve_instance *svinst, const char *name);
diff --git a/src/plugins/imap-filter-sieve/imap-filter-sieve.c b/src/plugins/imap-filter-sieve/imap-filter-sieve.c
index cc3cb51ab11936c2fe4d85d3966b7c483d58de95..150f860615c76c715b2400b2b2c13d213f8063d9 100644
--- a/src/plugins/imap-filter-sieve/imap-filter-sieve.c
+++ b/src/plugins/imap-filter-sieve/imap-filter-sieve.c
@@ -1041,8 +1041,8 @@ imap_sieve_filter_get_msgdata(struct imap_filter_sieve_context *sctx,
 		       "Failed to parse Delivered-To header");
 	}
 	if (rcpt_to == NULL) {
-		if (svinst->user_email != NULL)
-			rcpt_to = svinst->user_email;
+		if (svinst->set->parsed.user_email != NULL)
+			rcpt_to = svinst->set->parsed.user_email;
 		else if (smtp_address_parse_username(sctx->pool, user->username,
 						     &user_addr, &error) < 0) {
 			e_warning(sieve_get_event(svinst),
diff --git a/src/testsuite/cmd-test-config.c b/src/testsuite/cmd-test-config.c
index a6eabdf3108355aa9a0bc8b94fc6ba4e30485302..565256d94ee812ddb7de3e2739458f7fa6e0ca99 100644
--- a/src/testsuite/cmd-test-config.c
+++ b/src/testsuite/cmd-test-config.c
@@ -477,7 +477,11 @@ cmd_test_config_reload_operation_execute(const struct sieve_runtime_env *renv,
 				"reload configuration for sieve engine");
 		}
 
-		sieve_settings_load(eenv->svinst);
+		if (sieve_settings_reload(eenv->svinst) < 0) {
+			return testsuite_test_fail_cstr(
+				renv, "test_config_reload: "
+				"failed to reload sieve engine settings");
+		}
 	} else {
 		if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
 			sieve_runtime_trace(
diff --git a/src/testsuite/testsuite-settings.c b/src/testsuite/testsuite-settings.c
index 51d7d3eeb7b9cf32c09861f8d3d87667c7cb1852..a1b6b5c6ca85982d1861176af32c27bfe31fed38 100644
--- a/src/testsuite/testsuite-settings.c
+++ b/src/testsuite/testsuite-settings.c
@@ -5,9 +5,12 @@
 #include "hash.h"
 #include "imem.h"
 #include "strfuncs.h"
+#include "settings.h"
 #include "mail-user.h"
 
 #include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-tool.h"
 
 #include "testsuite-common.h"
 #include "testsuite-mailstore.h"
@@ -51,12 +54,30 @@ void testsuite_settings_deinit(void)
 }
 
 static const char *
-testsuite_setting_get(struct sieve_instance *svinst ATTR_UNUSED,
-		      void *context ATTR_UNUSED, const char *identifier)
+testsuite_setting_get(struct sieve_instance *svinst, void *context ATTR_UNUSED,
+		      const char *identifier)
 {
+	const struct sieve_settings *svset = svinst->set;
 	struct testsuite_setting *setting;
 	struct mail_user *user;
 
+	if (strcmp(identifier, "sieve_max_script_size") == 0)
+		return t_strdup_printf("%zu", svset->max_script_size);
+	else if (strcmp(identifier, "sieve_max_actions") == 0)
+		return t_strdup_printf("%u", svset->max_actions);
+	else if (strcmp(identifier, "sieve_max_redirects") == 0)
+		return t_strdup_printf("%u", svset->max_redirects);
+	else if (strcmp(identifier, "sieve_max_cpu_time") == 0)
+		return t_strdup_printf("%u", svset->max_cpu_time);
+	else if (strcmp(identifier, "sieve_resource_usage_timeout") == 0)
+		return t_strdup_printf("%u", svset->resource_usage_timeout);
+	else if (strcmp(identifier, "sieve_redirect_envelope_from") == 0)
+		return svset->redirect_envelope_from;
+	else if (strcmp(identifier, "sieve_redirect_duplicate_period") == 0)
+		return t_strdup_printf("%u", svset->redirect_duplicate_period);
+	else if (strcmp(identifier, "sieve_user_email") == 0)
+		return svset->user_email;
+
 	setting = hash_table_lookup(settings, identifier);
 	if (setting != NULL)
 		return setting->value;
@@ -69,6 +90,18 @@ testsuite_setting_get(struct sieve_instance *svinst ATTR_UNUSED,
 
 void testsuite_setting_set(const char *identifier, const char *value)
 {
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+
+	if (svinst != NULL) {
+		struct settings_root *set_root;
+
+		set_root = settings_root_find(svinst->event);
+		settings_root_override_remove(set_root, identifier,
+					      SETTINGS_OVERRIDE_TYPE_CODE);
+		settings_root_override(set_root, identifier, value,
+				       SETTINGS_OVERRIDE_TYPE_CODE);
+	}
+
 	struct testsuite_setting *setting =
 		hash_table_lookup(settings, identifier);
 
@@ -86,6 +119,16 @@ void testsuite_setting_set(const char *identifier, const char *value)
 
 void testsuite_setting_unset(const char *identifier)
 {
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+
+	if (svinst != NULL) {
+		struct settings_root *set_root;
+
+		set_root = settings_root_find(svinst->event);
+		settings_root_override_remove(set_root, identifier,
+					      SETTINGS_OVERRIDE_TYPE_CODE);
+	}
+
 	struct testsuite_setting *setting =
 		hash_table_lookup(settings, identifier);
 
diff --git a/tests/execute/errors-cpu-limit.svtest b/tests/execute/errors-cpu-limit.svtest
index 4a045bc46e3494b5f7efa0e83525be9f013399aa..a87375dc48c51149490830b91c5544e3441dcb25 100644
--- a/tests/execute/errors-cpu-limit.svtest
+++ b/tests/execute/errors-cpu-limit.svtest
@@ -1,6 +1,6 @@
 require "vnd.dovecot.testsuite";
 
-test_config_set "sieve_max_cpu_time" "2";
+test_config_set "sieve_max_cpu_time" "2s";
 test_config_reload;
 
 test_set "message" text: