diff --git a/Makefile.am b/Makefile.am
index 865fdf6a2d96fe4d6e5c32078c01cca76d5625b2..3d1ff922ff6fdec80a9af8e109a45492d7550b0e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -172,6 +172,8 @@ test_cases = \
 	tests/extensions/mime/foreverypart.svtest \
 	tests/extensions/mime/extracttext.svtest \
 	tests/extensions/mime/calendar-example.svtest \
+	tests/extensions/special-use/errors.svtest \
+	tests/extensions/special-use/execute.svtest \
 	tests/extensions/vnd.dovecot/debug/execute.svtest \
 	tests/extensions/vnd.dovecot/environment/basic.svtest \
 	tests/extensions/vnd.dovecot/environment/variables.svtest \
diff --git a/configure.ac b/configure.ac
index 551cddf685a6675e021c9a0188252f01fbb7286a..32fa6d6e7f670063aa1e56efa43fee03327cf187 100644
--- a/configure.ac
+++ b/configure.ac
@@ -212,6 +212,7 @@ src/lib-sieve/plugins/metadata/Makefile
 src/lib-sieve/plugins/duplicate/Makefile
 src/lib-sieve/plugins/index/Makefile
 src/lib-sieve/plugins/mime/Makefile
+src/lib-sieve/plugins/special-use/Makefile
 src/lib-sieve/plugins/vnd.dovecot/Makefile
 src/lib-sieve/plugins/vnd.dovecot/debug/Makefile
 src/lib-sieve/plugins/vnd.dovecot/environment/Makefile
diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index 91c3c08cc8f1b9be0c08da5e67096a2742bc813d..2e80871bb39b60852f9e422946a739f0e0a8bc22 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -78,6 +78,7 @@ plugins = \
 	$(extdir)/index/libsieve_ext_index.la \
 	$(extdir)/metadata/libsieve_ext_metadata.la \
 	$(extdir)/mime/libsieve_ext_mime.la \
+	$(extdir)/special-use/libsieve_ext_special_use.la \
 	$(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
 	$(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
 	$(extdir)/vnd.dovecot/report/libsieve_ext_vnd_report.la \
diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am
index 887235f1c1508003ca670403fc9b1e7a6cbf79fa..2a1c0548f7caf506670da0419e53caa8e29dcdd3 100644
--- a/src/lib-sieve/plugins/Makefile.am
+++ b/src/lib-sieve/plugins/Makefile.am
@@ -25,6 +25,7 @@ SUBDIRS = \
 	index \
 	metadata \
 	mime \
+	special-use \
 	vnd.dovecot \
 	$(UNFINISHED)
 
diff --git a/src/lib-sieve/plugins/special-use/Makefile.am b/src/lib-sieve/plugins/special-use/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..0f31aeee12ea74e40493af1a200c9f182dec5306
--- /dev/null
+++ b/src/lib-sieve/plugins/special-use/Makefile.am
@@ -0,0 +1,22 @@
+noinst_LTLIBRARIES = libsieve_ext_special_use.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tags = \
+	tag-specialuse.c
+
+tests = \
+	tst-specialuse-exists.c
+
+libsieve_ext_special_use_la_SOURCES = \
+	$(tags) \
+	$(tests) \
+	ext-special-use-common.c \
+	ext-special-use.c
+
+headers = \
+	ext-special-use-common.h
+
+noinst_HEADERS = $(headers)
diff --git a/src/lib-sieve/plugins/special-use/ext-special-use-common.c b/src/lib-sieve/plugins/special-use/ext-special-use-common.c
new file mode 100644
index 0000000000000000000000000000000000000000..fcaf1b57d7b7e923fa81cef2b3b14d2f6c07b99e
--- /dev/null
+++ b/src/lib-sieve/plugins/special-use/ext-special-use-common.c
@@ -0,0 +1,31 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-arg.h"
+
+#include "ext-special-use-common.h"
+
+bool ext_special_use_flag_valid(const char *flag)
+{
+	const char *p = flag;
+
+	/* RFC 6154, Section 6:
+
+	   use-attr        =  "\All" / "\Archive" / "\Drafts" / "\Flagged" /
+	                      "\Junk" / "\Sent" / "\Trash" / use-attr-ext
+	   use-attr-ext    =  "\" atom
+	 */
+
+	/* "\" */
+	if (*p != '\\')
+		return FALSE;
+	p++;
+
+	/* atom */
+	for (; *p != '\0'; p++) {
+		if (!IS_ATOM_CHAR(*p))
+			return FALSE;
+	}
+
+	return TRUE;
+}
diff --git a/src/lib-sieve/plugins/special-use/ext-special-use-common.h b/src/lib-sieve/plugins/special-use/ext-special-use-common.h
new file mode 100644
index 0000000000000000000000000000000000000000..44d0e1eb1e0b4e739916ede44f78f3496d327816
--- /dev/null
+++ b/src/lib-sieve/plugins/special-use/ext-special-use-common.h
@@ -0,0 +1,43 @@
+#ifndef EXT_SPECIAL_USE_COMMON_H
+#define EXT_SPECIAL_USE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def specialuse_tag;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def specialuse_exists_test;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def specialuse_operand;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def specialuse_exists_operation;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def special_use_extension;
+
+/*
+ * Flag checking
+ */
+
+bool ext_special_use_flag_valid(const char *flag);
+
+#endif /* __EXT_MAILBOX_COMMON_H */
+
diff --git a/src/lib-sieve/plugins/special-use/ext-special-use.c b/src/lib-sieve/plugins/special-use/ext-special-use.c
new file mode 100644
index 0000000000000000000000000000000000000000..7cf92a38bb1c6bc311fdb52172d033f67b4dc517
--- /dev/null
+++ b/src/lib-sieve/plugins/special-use/ext-special-use.c
@@ -0,0 +1,57 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */
+
+/* Extension special-use
+ * ---------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC ----
+ * Implementation: prototype
+ * Status: development
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-special-use-common.h"
+
+/*
+ * Extension
+ */
+
+static bool
+ext_special_use_validator_load(const struct sieve_extension *ext,
+			       struct sieve_validator *valdtr);
+
+const struct sieve_extension_def special_use_extension = {
+	.name = "special-use",
+	.validator_load = ext_special_use_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(specialuse_exists_operation),
+	SIEVE_EXT_DEFINE_OPERAND(specialuse_operand)
+};
+
+static bool
+ext_special_use_validator_load(const struct sieve_extension *ext,
+			       struct sieve_validator *valdtr)
+{
+	/* Register :specialuse tag with fileinto command and we don't care
+	   whether this command is registered or even whether it will be
+	   registered at all. The validator handles either situation gracefully.
+	 */
+	sieve_validator_register_external_tag(
+		valdtr, "fileinto", ext, &specialuse_tag,
+		SIEVE_OPT_SIDE_EFFECT);
+
+	/* Register new test */
+	sieve_validator_register_command(valdtr, ext, &specialuse_exists_test);
+
+	return TRUE;
+}
diff --git a/src/lib-sieve/plugins/special-use/tag-specialuse.c b/src/lib-sieve/plugins/special-use/tag-specialuse.c
new file mode 100644
index 0000000000000000000000000000000000000000..ce9fbeec82bc4bae5e5b9b3cbfeb9fdc895f833e
--- /dev/null
+++ b/src/lib-sieve/plugins/special-use/tag-specialuse.c
@@ -0,0 +1,313 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-special-use-common.h"
+
+/*
+ * Flags tagged argument
+ */
+
+static bool
+tag_specialuse_validate(struct sieve_validator *valdtr,
+			struct sieve_ast_argument **arg,
+			struct sieve_command *cmd);
+static bool
+tag_specialuse_generate(const struct sieve_codegen_env *cgenv,
+			struct sieve_ast_argument *arg,
+			struct sieve_command *cmd);
+
+const struct sieve_argument_def specialuse_tag = {
+	.identifier = "specialuse",
+	.validate = tag_specialuse_validate,
+	.generate = tag_specialuse_generate
+};
+
+/*
+ * Side effect
+ */
+
+static bool
+seff_specialuse_dump_context(const struct sieve_side_effect *seffect,
+			     const struct sieve_dumptime_env *denv,
+			     sieve_size_t *address);
+static int
+seff_specialuse_read_context(const struct sieve_side_effect *seffect,
+			     const struct sieve_runtime_env *renv,
+			     sieve_size_t *address, void **context);
+
+static int
+seff_specialuse_merge(const struct sieve_runtime_env *renv,
+		      const struct sieve_action *action,
+		      const struct sieve_side_effect *old_seffect,
+		      const struct sieve_side_effect *new_seffect,
+		      void **old_context);
+
+static void
+seff_specialuse_print(const struct sieve_side_effect *seffect,
+		      const struct sieve_action *action,
+		      const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+seff_specialuse_pre_execute(const struct sieve_side_effect *seffect,
+			    const struct sieve_action_exec_env *aenv,
+			    void **context, void *tr_context);
+
+const struct sieve_side_effect_def specialuse_side_effect = {
+	SIEVE_OBJECT("specialuse", &specialuse_operand, 0),
+	.precedence = 200,
+	.to_action = &act_store,
+	.dump_context = seff_specialuse_dump_context,
+	.read_context = seff_specialuse_read_context,
+	.merge = seff_specialuse_merge,
+	.print = seff_specialuse_print,
+	.pre_execute = seff_specialuse_pre_execute
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+	SIEVE_EXT_DEFINE_SIDE_EFFECT(specialuse_side_effect);
+
+const struct sieve_operand_def specialuse_operand = {
+	.name = "specialuse operand",
+	.ext_def = &special_use_extension,
+	.class = &sieve_side_effect_operand_class,
+	.interface = &ext_side_effects
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+tag_specialuse_validate(struct sieve_validator *valdtr,
+			struct sieve_ast_argument **arg,
+			struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	const char *use_flag;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* Check syntax:
+	 *   :specialuse <special-use-flag: string>
+	 */
+	if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+					  SAAT_STRING, FALSE))
+		return FALSE;
+	use_flag = sieve_ast_argument_strc(*arg);
+	if (!ext_special_use_flag_valid(use_flag)) {
+		sieve_argument_validate_error(
+			valdtr, *arg, "special-use tag: "
+			"invalid special-use flag `%s' specified",
+			str_sanitize(use_flag, 64));
+		return FALSE;
+	}
+
+	tag->parameters = *arg;
+
+	/* Detach parameter */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tag_specialuse_generate(const struct sieve_codegen_env *cgenv,
+			struct sieve_ast_argument *arg,
+			struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *param;
+
+	if (sieve_ast_argument_type(arg) != SAAT_TAG)
+		return FALSE;
+
+	sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext,
+				   &specialuse_side_effect);
+
+	/* Explicit :specialuse tag */
+	param = arg->parameters;
+
+	/* Call the generation function for the argument */
+	if (param->argument != NULL && param->argument->def != NULL &&
+	    param->argument->def->generate != NULL &&
+	    !param->argument->def->generate(cgenv, param, cmd))
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+/* Context data */
+
+struct seff_specialuse_context {
+	const char *special_use_flag;
+};
+
+/* Context coding */
+
+static bool
+seff_specialuse_dump_context(
+	const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	return sieve_opr_stringlist_dump(denv, address, "specialuse");
+}
+
+static int
+seff_specialuse_read_context(
+	const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address,
+	void **se_context)
+{
+	pool_t pool = sieve_result_pool(renv->result);
+	struct seff_specialuse_context *ctx;
+	string_t *special_use_flag;
+	const char *use_flag;
+	int ret;
+
+	if ((ret = sieve_opr_string_read(renv, address, "specialuse",
+					 &special_use_flag)) <= 0)
+		return ret;
+
+	use_flag = str_c(special_use_flag);
+	if (!ext_special_use_flag_valid(use_flag)) {
+		sieve_runtime_error(
+			renv, NULL, "special-use_tag: "
+			"invalid special-use flag `%s' specified",
+			str_sanitize(use_flag, 64));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	ctx = p_new(pool, struct seff_specialuse_context, 1);
+	ctx->special_use_flag = p_strdup(pool, use_flag);
+
+	*se_context = (void *) ctx;
+
+	return SIEVE_EXEC_OK;
+}
+
+/* Result verification */
+
+static int
+seff_specialuse_merge(const struct sieve_runtime_env *renv ATTR_UNUSED,
+		      const struct sieve_action *action ATTR_UNUSED,
+		      const struct sieve_side_effect *old_seffect ATTR_UNUSED,
+		      const struct sieve_side_effect *new_seffect,
+		      void **old_context)
+{
+	if (new_seffect != NULL)
+		*old_context = new_seffect->context;
+
+	return 1;
+}
+
+/* Result printing */
+
+static void
+seff_specialuse_print(const struct sieve_side_effect *seffect,
+		      const struct sieve_action *action ATTR_UNUSED,
+		      const struct sieve_result_print_env *rpenv,
+		      bool *keep ATTR_UNUSED)
+{
+	struct seff_specialuse_context *ctx =
+		(struct seff_specialuse_context *)seffect->context;
+
+	sieve_result_seffect_printf(
+		rpenv,
+		"use mailbox with special-use flag `%s' instead if accessible",
+		ctx->special_use_flag);
+}
+
+/* Result execution */
+
+static int
+seff_specialuse_pre_execute(const struct sieve_side_effect *seffect,
+			    const struct sieve_action_exec_env *aenv,
+			    void **context ATTR_UNUSED, void *tr_context)
+{
+	struct seff_specialuse_context *ctx =
+		(struct seff_specialuse_context *)seffect->context;
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+	struct act_store_transaction *trans =
+		(struct act_store_transaction *)tr_context;
+	struct mailbox *box;
+
+	if (trans->box == NULL || trans->disabled)
+		return SIEVE_EXEC_OK;
+
+	/* Check whether something already failed */
+	switch (trans->error_code) {
+	case MAIL_ERROR_NONE:
+		break;
+	case MAIL_ERROR_TEMP:
+		return SIEVE_EXEC_TEMP_FAILURE;
+	default:
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	trans->error = NULL;
+	trans->error_code = MAIL_ERROR_NONE;
+
+	box = mailbox_alloc_for_user(eenv->scriptenv->user,
+				     ctx->special_use_flag,
+				     (MAILBOX_FLAG_POST_SESSION |
+				      MAILBOX_FLAG_SPECIAL_USE));
+
+	/* We still override the allocate default mailbox with ours below even
+	   when the default and special-use mailbox are identical. Choosing
+	   either one is (currently) equal and setting trans->mailbox_identifier
+	   for SPECIAL-USE needs to be done either way, so we use the same code
+	   path. */
+
+	/* Try to open the mailbox */
+	eenv->exec_status->last_storage = mailbox_get_storage(box);
+	if (mailbox_open(box) == 0) {
+		pool_t pool = sieve_result_pool(aenv->result);
+
+		/* Success */
+		mailbox_free(&trans->box);
+		trans->box = box;
+		trans->mailbox_identifier = p_strdup_printf(pool,
+			"[SPECIAL-USE %s]", ctx->special_use_flag);
+
+	} else {
+		/* Failure */
+		if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) {
+			/* Not found; revert to default */
+			mailbox_free(&box);
+		} else {
+			/* Total failure */
+			mailbox_free(&trans->box);
+			trans->box = box;
+			sieve_act_store_get_storage_error(aenv, trans);
+			return (trans->error_code == MAIL_ERROR_TEMP ?
+				SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+		}
+	}
+
+	return SIEVE_EXEC_OK;
+}
diff --git a/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c b/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c
new file mode 100644
index 0000000000000000000000000000000000000000..fbed3058def0b81e20c0f81b7891c21bed84a205
--- /dev/null
+++ b/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c
@@ -0,0 +1,482 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-actions.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-special-use-common.h"
+
+/*
+ * specialuse_exists command
+ *
+ * Syntax:
+ *    specialuse_exists [<mailbox: string>]
+ *                      <special-use-flags: string-list>
+ */
+
+static bool
+tst_specialuse_exists_validate(struct sieve_validator *valdtr,
+			       struct sieve_command *tst);
+static bool
+tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv,
+			       struct sieve_command *ctx);
+
+const struct sieve_command_def specialuse_exists_test = {
+	.identifier = "specialuse_exists",
+	.type = SCT_TEST,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_specialuse_exists_validate,
+	.generate = tst_specialuse_exists_generate
+};
+
+/*
+ * Mailboxexists operation
+ */
+
+static bool
+tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv,
+				     sieve_size_t *address);
+static int
+tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv,
+					sieve_size_t *address);
+
+const struct sieve_operation_def specialuse_exists_operation = {
+	.mnemonic = "SPECIALUSE_EXISTS",
+	.ext_def = &special_use_extension,
+	.dump = tst_specialuse_exists_operation_dump,
+	.execute = tst_specialuse_exists_operation_execute
+};
+
+/*
+ * Test validation
+ */
+
+struct _validate_context {
+	struct sieve_validator *valdtr;
+	struct sieve_command *tst;
+};
+
+static int
+tst_specialuse_exists_flag_validate(void *context,
+				    struct sieve_ast_argument *arg)
+{
+	struct _validate_context *valctx = (struct _validate_context *)context;
+
+	if (sieve_argument_is_string_literal(arg)) {
+		const char *flag = sieve_ast_argument_strc(arg);
+
+		if (!ext_special_use_flag_valid(flag)) {
+			sieve_argument_validate_error(
+				valctx->valdtr, arg, "%s test: "
+				"invalid special-use flag `%s' specified",
+				sieve_command_identifier(valctx->tst),
+				str_sanitize(flag, 64));
+		}
+	}
+
+	return 1;
+}
+
+static bool
+tst_specialuse_exists_validate(struct sieve_validator *valdtr,
+			       struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *arg2;
+	struct sieve_ast_argument *aarg;
+	struct _validate_context valctx;
+
+	if (arg == NULL) {
+		sieve_command_validate_error(
+			valdtr, tst, "the %s %s expects at least one argument, "
+			"but none was found",
+			sieve_command_identifier(tst),
+			sieve_command_type_name(tst));
+		return FALSE;
+	}
+
+	if (sieve_ast_argument_type(arg) != SAAT_STRING &&
+	    sieve_ast_argument_type(arg) != SAAT_STRING_LIST) {
+		sieve_argument_validate_error(
+			valdtr, arg,
+			"the %s %s expects either a string (mailbox) or "
+			"a string-list (special-use flags) as first argument, "
+			"but %s was found",
+			sieve_command_identifier(tst),
+			sieve_command_type_name(tst),
+			sieve_ast_argument_name(arg));
+		return FALSE;
+	}
+
+	arg2 = sieve_ast_argument_next(arg);
+	if (arg2 != NULL) {
+		/* First, check syntax sanity */
+		if (sieve_ast_argument_type(arg) != SAAT_STRING) {
+			sieve_argument_validate_error(
+				valdtr, arg,
+				"if a second argument is specified for the %s %s, the first "
+				"must be a string (mailbox), but %s was found",
+				sieve_command_identifier(tst),
+				sieve_command_type_name(tst),
+				sieve_ast_argument_name(arg));
+			return FALSE;
+		}
+		if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+			return FALSE;
+
+		if (sieve_ast_argument_type(arg2) != SAAT_STRING &&
+		    sieve_ast_argument_type(arg2) != SAAT_STRING_LIST)
+		{
+			sieve_argument_validate_error(
+				valdtr, arg2,
+				"the %s %s expects a string list (special-use flags) as "
+				"second argument when two arguments are specified, "
+				"but %s was found",
+				sieve_command_identifier(tst),
+				sieve_command_type_name(tst),
+				sieve_ast_argument_name(arg2));
+			return FALSE;
+		}
+	} else
+		arg2 = arg;
+
+	if (!sieve_validator_argument_activate(valdtr, tst, arg2, FALSE))
+		return FALSE;
+
+	aarg = arg2;
+	memset(&valctx, 0, sizeof(valctx));
+	valctx.valdtr = valdtr;
+	valctx.tst = tst;
+
+	return (sieve_ast_stringlist_map(
+		&aarg,	(void*)&valctx,
+		tst_specialuse_exists_flag_validate) >= 0);
+}
+
+/*
+ * Test generation
+ */
+
+static bool
+tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv,
+			       struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *arg2;
+
+	sieve_operation_emit(cgenv->sblock,
+		tst->ext, &specialuse_exists_operation);
+
+	/* Generate arguments */
+	arg2 = sieve_ast_argument_next(arg);
+	if (arg2 != NULL) {
+		if (!sieve_generate_argument(cgenv, arg, tst))
+			return FALSE;
+	} else {
+		sieve_opr_omitted_emit(cgenv->sblock);
+		arg2 = arg;
+	}
+	return sieve_generate_argument(cgenv, arg2, tst);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv,
+				     sieve_size_t *address)
+{
+	struct sieve_operand oprnd;
+
+	sieve_code_dumpf(denv, "SPECIALUSE_EXISTS");
+	sieve_code_descend(denv);
+
+	sieve_code_mark(denv);
+	if (!sieve_operand_read(denv->sblock, address, NULL, &oprnd)) {
+		sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+		return FALSE;
+	}
+
+	if (!sieve_operand_is_omitted(&oprnd)) {
+		return (sieve_opr_string_dump_data(denv, &oprnd,
+						   address, "mailbox") &&
+		        sieve_opr_stringlist_dump(denv, address,
+						  "special-use-flags"));
+	}
+
+	return sieve_opr_stringlist_dump(denv, address, "special-use-flags");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_specialuse_find_mailbox(const struct sieve_runtime_env *renv,
+			    const char *mailbox, struct mailbox **box_r)
+{
+	const struct sieve_execute_env *eenv = renv->exec_env;
+	struct mail_user *user = eenv->scriptenv->user;
+	struct mailbox *box;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	enum mail_error error_code;
+	const char *error;
+
+	*box_r = NULL;
+
+	if (user == NULL)
+		return 0;
+
+	/* Open the box */
+	box = mailbox_alloc_for_user(user, mailbox, MAILBOX_FLAG_POST_SESSION);
+	if (mailbox_open(box) < 0) {
+		error = mailbox_get_last_error(box, &error_code);
+
+		if (trace) {
+			sieve_runtime_trace(
+				renv, 0, "mailbox `%s' cannot be opened: %s",
+				str_sanitize(mailbox, 256), error);
+		}
+
+		mailbox_free(&box);
+
+		if (error_code == MAIL_ERROR_TEMP) {
+			sieve_runtime_error(
+				renv, NULL,	"specialuse_exists test: "
+				"failed to open mailbox `%s': %s",
+				str_sanitize(mailbox, 256), error);
+			return -1;
+		}
+		return 0;
+	}
+
+	/* Also fail when it is readonly */
+	if (mailbox_is_readonly(box)) {
+		if (trace) {
+			sieve_runtime_trace(
+				renv, 0, "mailbox `%s' is read-only",
+				str_sanitize(mailbox, 256));
+		}
+
+		mailbox_free(&box);
+		return 0;
+	}
+
+	*box_r = box;
+	return 1;
+}
+
+static int
+tst_specialuse_find_specialuse(const struct sieve_runtime_env *renv,
+			       const char *special_use)
+{
+	const struct sieve_execute_env *eenv = renv->exec_env;
+	struct mail_user *user = eenv->scriptenv->user;
+	struct mailbox *box;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	enum mail_error error_code;
+	const char *error;
+
+	if (user == NULL)
+		return 0;
+
+	/* Open the box */
+	box = mailbox_alloc_for_user(user, special_use,
+				     (MAILBOX_FLAG_POST_SESSION |
+				      MAILBOX_FLAG_SPECIAL_USE));
+	if (mailbox_open(box) < 0) {
+		error = mailbox_get_last_error(box, &error_code);
+
+		if (trace) {
+			sieve_runtime_trace(
+				renv, 0, "mailbox with special-use flag `%s' "
+				"cannot be opened: %s",
+				str_sanitize(special_use, 64), error);
+		}
+
+		mailbox_free(&box);
+
+		if (error_code == MAIL_ERROR_TEMP) {
+			sieve_runtime_error(
+				renv, NULL, "specialuse_exists test: "
+				"failed to open mailbox with special-use flag`%s': %s",
+				str_sanitize(special_use, 64), error);
+			return -1;
+		}
+		return 0;
+	}
+
+	/* Also fail when it is readonly */
+	if (mailbox_is_readonly(box)) {
+		if (trace) {
+			sieve_runtime_trace(
+				renv, 0,
+				"mailbox with special-use flag `%s' is read-only",
+				str_sanitize(special_use, 64));
+		}
+
+		mailbox_free(&box);
+		return 0;
+	}
+
+	mailbox_free(&box);
+	return 1;
+}
+
+static int
+tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv,
+					sieve_size_t *address)
+{
+	struct sieve_operand oprnd;
+	struct sieve_stringlist *special_use_flags;
+	string_t *mailbox;
+	struct mailbox *box = NULL;
+	bool trace = FALSE, all_exist = TRUE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read bare operand (two types possible) */
+	if ((ret = sieve_operand_runtime_read(renv, address,
+					      NULL, &oprnd)) <= 0)
+		return ret;
+
+	/* Mailbox operand (optional) */
+	mailbox = NULL;
+	if (!sieve_operand_is_omitted(&oprnd)) {
+		/* Read the mailbox operand */
+		if ((ret = sieve_opr_string_read_data(
+			renv, &oprnd, address, "mailbox", &mailbox)) <= 0)
+			return ret;
+
+		/* Read flag list */
+		if ((ret = sieve_opr_stringlist_read(
+			renv, address, "special-use-flags",
+			&special_use_flags)) <= 0)
+			return ret;
+
+	/* Flag-list operand */
+	} else {
+		/* Read flag list */
+		if ((ret = sieve_opr_stringlist_read(
+			renv, address, "special-use-flags",
+			&special_use_flags)) <= 0)
+			return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
+		sieve_runtime_trace(renv, 0, "specialuse_exists test");
+		sieve_runtime_trace_descend(renv);
+
+		trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	}
+
+	if (mailbox != NULL) {
+		if (tst_specialuse_find_mailbox(renv, str_c(mailbox), &box) < 0)
+			return SIEVE_EXEC_TEMP_FAILURE;
+	}
+
+	ret = 0;
+	if (box == NULL && mailbox != NULL) {
+		all_exist = FALSE;
+		sieve_runtime_trace(renv, 0,
+			"mailbox `%s' is not accessible",
+			str_sanitize(str_c(mailbox), 80));
+	} else {
+		string_t *special_use_flag;
+
+		if (mailbox != NULL) {
+			sieve_runtime_trace(
+				renv, 0, "mailbox `%s' is accessible",
+				str_sanitize(str_c(mailbox), 80));
+		}
+
+		special_use_flag = NULL;
+		while ((ret = sieve_stringlist_next_item(
+			special_use_flags, &special_use_flag)) > 0) {
+			const char *use_flag = str_c(special_use_flag);
+
+			if (!ext_special_use_flag_valid(use_flag)) {
+				sieve_runtime_error(
+					renv, NULL, "specialuse_exists test: "
+					"invalid special-use flag `%s' specified",
+					str_sanitize(use_flag, 64));
+				if (box != NULL) {
+					/* Close mailbox */
+					mailbox_free(&box);
+				}
+				return SIEVE_EXEC_FAILURE;
+			}
+
+			if (box != NULL) {
+				/* Mailbox has this SPECIAL-USE flag? */
+				if (!mailbox_has_special_use(box, use_flag)) {
+					all_exist = FALSE;
+					break;
+				}
+			} else {
+				/* Is there mailbox with this SPECIAL-USE flag? */
+				if ((ret = tst_specialuse_find_specialuse(
+					renv, use_flag)) <= 0) {
+					if (ret < 0)
+						return SIEVE_EXEC_TEMP_FAILURE;
+					all_exist = FALSE;
+					break;
+				}
+			}
+
+			if (trace) {
+				sieve_runtime_trace(
+					renv, 0, "special-use flag `%s' exists",
+					str_sanitize(use_flag, 80));
+			}
+		}
+	}
+
+	if (box != NULL) {
+		/* Close mailbox */
+		mailbox_free(&box);
+	}
+
+	if (ret < 0) {
+		sieve_runtime_trace_error(
+			renv, "invalid special-use flag item");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if (trace) {
+		if (all_exist) {
+			sieve_runtime_trace(
+				renv, 0, "all special-use flags are set");
+		} else {
+			sieve_runtime_trace(
+				renv, 0, "some special-use are not set");
+		}
+	}
+
+	sieve_interpreter_set_test_result(renv->interp, all_exist);
+	return SIEVE_EXEC_OK;
+}
diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c
index 398e616261bd7c0c5adb5fa53bb9150068f2f69f..a1cb810560838277908f51c3e4a3e2a3155d9dd3 100644
--- a/src/lib-sieve/sieve-extensions.c
+++ b/src/lib-sieve/sieve-extensions.c
@@ -141,6 +141,7 @@ extern const struct sieve_extension_def spamtest_extension;
 extern const struct sieve_extension_def spamtestplus_extension;
 extern const struct sieve_extension_def virustest_extension;
 extern const struct sieve_extension_def editheader_extension;
+extern const struct sieve_extension_def special_use_extension;
 
 extern const struct sieve_extension_def vnd_debug_extension;
 extern const struct sieve_extension_def vnd_environment_extension;
@@ -150,6 +151,7 @@ const struct sieve_extension_def *sieve_extra_extensions[] = {
 	&vacation_seconds_extension, &spamtest_extension, &spamtestplus_extension,
 	&virustest_extension, &editheader_extension,
 	&mboxmetadata_extension, &servermetadata_extension,
+	&special_use_extension,
 
 	/* vnd.dovecot. */
 	&vnd_debug_extension, &vnd_environment_extension, &vnd_report_extension
diff --git a/tests/extensions/special-use/errors.svtest b/tests/extensions/special-use/errors.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..a0148a0ce1e7b36a01ef3d901614e45f73346cc5
--- /dev/null
+++ b/tests/extensions/special-use/errors.svtest
@@ -0,0 +1,20 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Invalid syntax
+ */
+
+test "Invalid Syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "14" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+
diff --git a/tests/extensions/special-use/errors/syntax.sieve b/tests/extensions/special-use/errors/syntax.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..fc28010096fc764c5c6a3167ed227849ae903106
--- /dev/null
+++ b/tests/extensions/special-use/errors/syntax.sieve
@@ -0,0 +1,32 @@
+require "special-use";
+require "fileinto";
+
+# 1
+if specialuse_exists {}
+# 2
+if specialuse_exists 3423 {}
+# 3
+if specialuse_exists :frop {}
+# 4
+if specialuse_exists 24234 "\\Sent" {}
+# 5
+if specialuse_exists "frop" 32234 {}
+# 6
+if specialuse_exists "frop" :friep {}
+
+# 7
+if specialuse_exists "frop" {}
+# 8
+if specialuse_exists "frop" ["frop"] {}
+
+# 9
+fileinto :specialuse "\\frop";
+# 10
+fileinto :specialuse 343 "\\frop";
+# 11
+fileinto :specialuse :create "\\frop";
+# 12
+fileinto :specialuse "\\frop" 234234;
+
+# 13
+fileinto :specialuse "frop" "frop";
diff --git a/tests/extensions/special-use/execute.svtest b/tests/extensions/special-use/execute.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..746f1d3e6f33410330b83eca9b86f936a65ef44a
--- /dev/null
+++ b/tests/extensions/special-use/execute.svtest
@@ -0,0 +1,35 @@
+require "vnd.dovecot.testsuite";
+require "special-use";
+require "fileinto";
+
+test "Specialuse_exists - None exist" {
+	if specialuse_exists "\\Sent" {
+		test_fail "specialuse_exists confirms existence of unassigned special-use flag";
+	}
+}
+
+test "Specialuse_exists <MAILBOX> - None exist" {
+	if specialuse_exists "INBOX" "\\Sent" {
+		test_fail "specialuse_exists confirms existence of unassigned special-use flag";
+	}
+}
+
+test_mailbox_create "frop";
+test_mailbox_create "friep";
+
+test ":specialuse" {
+	test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop 1
+
+Frop!
+.
+	;
+
+	fileinto :specialuse "\\Junk" "frop";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+}