diff --git a/src/lib-sieve/cmd-redirect.c b/src/lib-sieve/cmd-redirect.c
index f2cc0ece24bed7e71cb992f93765a1e49688c36e..25d2c162728d4960f3edad0bce6446c8749608de 100644
--- a/src/lib-sieve/cmd-redirect.c
+++ b/src/lib-sieve/cmd-redirect.c
@@ -559,8 +559,7 @@ act_redirect_execute(const struct sieve_action_exec_env *aenv,
 	struct mail *mail = (action->mail != NULL ?
 			     action->mail : sieve_message_get_mail(msgctx));
 	const struct sieve_message_data *msgdata = eenv->msgdata;
-	const struct sieve_script_env *senv = eenv->scriptenv;
-	bool loop_detected = FALSE;
+	bool duplicate, loop_detected = FALSE;
 	int ret;
 
 	/*
@@ -583,8 +582,19 @@ act_redirect_execute(const struct sieve_action_exec_env *aenv,
 	i_assert(trans->dupeid != NULL);
 
 	/* Check whether we've seen this message before */
-	if (sieve_action_duplicate_check(senv, trans->dupeid,
-					 strlen(trans->dupeid))) {
+	ret = sieve_action_duplicate_check(aenv, trans->dupeid,
+					   strlen(trans->dupeid),
+					   &duplicate);
+	if (ret < SIEVE_EXEC_OK) {
+		sieve_result_critical(
+			aenv, "failed to check for duplicate forward",
+			"failed to check for duplicate forward to <%s>%s",
+			smtp_address_encode(ctx->to_address),
+			(ret == SIEVE_EXEC_TEMP_FAILURE ?
+			 " (temporaty failure)" : ""));
+		return ret;
+	}
+	if (duplicate) {
 		sieve_result_global_log(
 			aenv, "discarded duplicate forward to <%s>",
 			smtp_address_encode(ctx->to_address));
@@ -624,7 +634,6 @@ act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
 	struct mail *mail = (action->mail != NULL ?
 			     action->mail : sieve_message_get_mail(msgctx));
 	struct act_redirect_transaction *trans = tr_context;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	int ret;
 
 	if (trans->skip_redirect)
@@ -639,7 +648,7 @@ act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
 		/* Mark this message id as forwarded to the specified
 		   destination */
 		sieve_action_duplicate_mark(
-			senv, trans->dupeid, strlen(trans->dupeid),
+			aenv, trans->dupeid, strlen(trans->dupeid),
 			ioloop_time + svinst->redirect_duplicate_period);
 
 		eenv->exec_status->significant_action_executed = TRUE;
diff --git a/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
index 7f10560c2240c8d8d0fc206aa3dd69b8c88631f1..c4e9159b3f1438c15c4cb294e2fd22e859474c03 100644
--- a/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
+++ b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
@@ -109,7 +109,6 @@ act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
 			  void *tr_context ATTR_UNUSED, int status)
 {
 	const struct sieve_execute_env *eenv = aenv->exec_env;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct act_duplicate_mark_data *data =
 		(struct act_duplicate_mark_data *)aenv->action->context;
 
@@ -120,7 +119,7 @@ act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
 	 * message.
 	 */
 	eenv->exec_status->significant_action_executed = TRUE;
-	sieve_action_duplicate_mark(senv, data->hash, sizeof(data->hash),
+	sieve_action_duplicate_mark(aenv, data->hash, sizeof(data->hash),
 				    ioloop_time + data->period);
 }
 
@@ -168,11 +167,10 @@ ext_duplicate_hash(string_t *handle, const char *value, size_t value_len,
 
 int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 			const char *value, size_t value_len,
-			sieve_number_t period, bool last)
+			sieve_number_t period, bool last, bool *duplicate_r)
 {
 	const struct sieve_execute_env *eenv = renv->exec_env;
 	const struct sieve_extension *this_ext = renv->oprtn->ext;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct ext_duplicate_context *rctx;
 	bool duplicate = FALSE;
 	pool_t msg_pool = NULL, result_pool = NULL;
@@ -180,16 +178,19 @@ int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 	struct ext_duplicate_hash *hash_record = NULL;
 	struct ext_duplicate_handle *handle_record = NULL;
 	struct act_duplicate_mark_data *act;
+	int ret;
 
-	if (!sieve_action_duplicate_check_available(senv)) {
+	*duplicate_r = FALSE;
+
+	if (!sieve_execute_duplicate_check_available(eenv)) {
 		sieve_runtime_warning(
 			renv, NULL, "duplicate test: "
 			"duplicate checking not available in this context");
-		return 0;
+		return SIEVE_EXEC_OK;
 	}
 
 	if (value == NULL)
-		return 0;
+		return SIEVE_EXEC_OK;
 
 	/* Create hash */
 	ext_duplicate_hash(handle, value, value_len, last, hash);
@@ -221,7 +222,9 @@ int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 				(handle == NULL ? NULL : str_c(handle));
 			if (null_strcmp(rhandle->handle, handle_str) == 0 &&
 			    rhandle->last == last)
-				return (rhandle->duplicate ? 1 : 0);
+				return (rhandle->duplicate ?
+				        SIEVE_DUPLICATE_CHECK_RESULT_EXISTS :
+					SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND);
 		}
 	}
 
@@ -234,15 +237,25 @@ int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 	act->last = last;
 
 	/* Check duplicate */
-	duplicate = sieve_action_duplicate_check(senv, hash, sizeof(hash));
-	if (!duplicate && last) {
+	ret = sieve_execute_duplicate_check(eenv, hash, sizeof(hash),
+					    &duplicate);
+	if (ret >= SIEVE_EXEC_OK && !duplicate && last) {
 		unsigned char no_last_hash[MD5_RESULTLEN];
 
 		/* Check for entry without :last */
 		ext_duplicate_hash(handle, value, value_len,
 				   FALSE, no_last_hash);
-		sieve_action_duplicate_check(senv, no_last_hash,
-					     sizeof(no_last_hash));
+		ret = sieve_execute_duplicate_check(
+			eenv, no_last_hash, sizeof(no_last_hash),
+			&duplicate);
+	}
+	if (ret < SIEVE_EXEC_OK) {
+		sieve_runtime_critical(
+			renv, NULL, "failed to check for duplicate",
+			"failed to check for duplicate%s",
+			(ret == SIEVE_EXEC_TEMP_FAILURE ?
+			 " (temporary failure)" : ""));
+		return ret;
 	}
 
 	/* We may only mark the message as duplicate when Sieve script executes
@@ -253,7 +266,7 @@ int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 		if (sieve_result_add_action(renv, NULL, NULL,
 					    &act_duplicate_mark,
 					    NULL, (void *) act, 0, FALSE) < 0)
-			return -1;
+			return SIEVE_EXEC_FAILURE;
 	}
 
 	/* Cache result */
@@ -273,6 +286,8 @@ int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 	handle_record->last = last;
 	handle_record->duplicate = duplicate;
 
-	return ( duplicate ? 1 : 0 );
+	*duplicate_r = duplicate;
+
+	return SIEVE_EXEC_OK;
 }
 
diff --git a/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
index 0bbb6b5a372520d4695345a526a41702ea9571aa..c802b0856da7d074ab635c0146682aa3612a3cd8 100644
--- a/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
+++ b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
@@ -36,6 +36,6 @@ extern const struct sieve_operation_def tst_duplicate_operation;
 
 int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 			const char *value, size_t value_len,
-			sieve_number_t period, bool last);
+			sieve_number_t period, bool last, bool *duplicate_r);
 
 #endif
diff --git a/src/lib-sieve/plugins/duplicate/tst-duplicate.c b/src/lib-sieve/plugins/duplicate/tst-duplicate.c
index 2a000de2bd960eff01a6b5aa9282730ba223a4a4..d211e0411d18755dc205aa84f3e022fb522c834d 100644
--- a/src/lib-sieve/plugins/duplicate/tst-duplicate.c
+++ b/src/lib-sieve/plugins/duplicate/tst-duplicate.c
@@ -428,10 +428,10 @@ tst_duplicate_operation_execute(const struct sieve_runtime_env *renv,
 	if (val == NULL) {
 		duplicate = FALSE;
 	} else {
-		if ((ret = ext_duplicate_check(renv, handle, val, val_len,
-					       seconds, last)) < 0)
-			return SIEVE_EXEC_FAILURE;
-		duplicate = (ret > 0);
+		ret = ext_duplicate_check(renv, handle, val, val_len,
+					  seconds, last, &duplicate);
+		if (ret < SIEVE_EXEC_OK)
+			return ret;
 	}
 
 	/* Trace */
diff --git a/src/lib-sieve/plugins/vacation/cmd-vacation.c b/src/lib-sieve/plugins/vacation/cmd-vacation.c
index 4367e64d0cc879b4aa367a81f8069471fdde2d37..ddc14be3b036ae9eb3f07dbe74ec0de341b8d347 100644
--- a/src/lib-sieve/plugins/vacation/cmd-vacation.c
+++ b/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -1233,7 +1233,6 @@ act_vacation_commit(const struct sieve_action_exec_env *aenv,
 	struct sieve_instance *svinst = eenv->svinst;
 	const struct ext_vacation_config *config =
 		(const struct ext_vacation_config *)ext->context;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct act_vacation_context *ctx =
 		(struct act_vacation_context *)action->context;
 	unsigned char dupl_hash[MD5_RESULTLEN];
@@ -1302,12 +1301,23 @@ act_vacation_commit(const struct sieve_action_exec_env *aenv,
 	}
 
 	/* Did whe respond to this user before? */
-	if (sieve_action_duplicate_check_available(senv)) {
+	if (sieve_action_duplicate_check_available(aenv)) {
+		bool duplicate;
+
 		act_vacation_hash(ctx, smtp_address_encode(sender), dupl_hash);
 
-		if (sieve_action_duplicate_check(senv, dupl_hash,
-						 sizeof(dupl_hash)))
-		{
+		ret = sieve_action_duplicate_check(aenv, dupl_hash,
+						   sizeof(dupl_hash),
+						   &duplicate);
+		if (ret < SIEVE_EXEC_OK) {
+			sieve_result_critical(
+				aenv, "failed to check for duplicate vacation response",
+				"failed to check for duplicate vacation response%s",
+				(ret == SIEVE_EXEC_TEMP_FAILURE ?
+				 " (temporaty failure)" : ""));
+			return ret;
+		}
+		if (duplicate) {
 			sieve_result_global_log(
 				aenv,
 				"discarded duplicate vacation response to <%s>",
@@ -1554,7 +1564,7 @@ act_vacation_commit(const struct sieve_action_exec_env *aenv,
 
 		/* Mark as replied */
 		if (seconds > 0) {
-			sieve_action_duplicate_mark(senv, dupl_hash,
+			sieve_action_duplicate_mark(aenv, dupl_hash,
 						    sizeof(dupl_hash),
 						    ioloop_time + seconds);
 		}
diff --git a/src/lib-sieve/sieve-actions.c b/src/lib-sieve/sieve-actions.c
index 7c775312ff23b1f27c141bd57b411f963ccf1a0e..86c9954eb8d16a8d12709eae3959d028e3fde854 100644
--- a/src/lib-sieve/sieve-actions.c
+++ b/src/lib-sieve/sieve-actions.c
@@ -927,39 +927,6 @@ int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
  * Action utility functions
  */
 
-/* Checking for duplicates */
-
-bool sieve_action_duplicate_check_available(
-	const struct sieve_script_env *senv)
-{
-	return (senv->duplicate_check != NULL && senv->duplicate_mark != NULL);
-}
-
-bool sieve_action_duplicate_check(const struct sieve_script_env *senv,
-				  const void *id, size_t id_size)
-{
-	if (senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
-		return FALSE;
-
-	return senv->duplicate_check(senv, id, id_size);
-}
-
-void sieve_action_duplicate_mark(const struct sieve_script_env *senv,
-				 const void *id, size_t id_size, time_t time)
-{
-	if (senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
-		return;
-
-	senv->duplicate_mark(senv, id, id_size, time);
-}
-
-void sieve_action_duplicate_flush(const struct sieve_script_env *senv)
-{
-	if (senv->duplicate_flush == NULL)
-		return;
-	senv->duplicate_flush(senv);
-}
-
 /* Rejecting the mail */
 
 static int
diff --git a/src/lib-sieve/sieve-actions.h b/src/lib-sieve/sieve-actions.h
index e3faa2df3b1d1f6eb9eabfe5fa389fbd32c3944d..f1a447fc3bc245392e9905ca27ed52371394c2dc 100644
--- a/src/lib-sieve/sieve-actions.h
+++ b/src/lib-sieve/sieve-actions.h
@@ -260,18 +260,40 @@ int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
 				     const struct smtp_address *to_address);
 
 /*
- * Action utility functions
+ * Checking for duplicates
  */
 
-/* Checking for duplicates */
+static inline bool
+sieve_action_duplicate_check_available(const struct sieve_action_exec_env *aenv)
+{
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+
+	return sieve_execute_duplicate_check_available(eenv);
+}
+
+static inline int
+sieve_action_duplicate_check(const struct sieve_action_exec_env *aenv,
+			     const void *id, size_t id_size,
+			     bool *duplicate_r)
+{
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+
+	return sieve_execute_duplicate_check(eenv, id, id_size,
+					     duplicate_r);
+}
+
+static inline void
+sieve_action_duplicate_mark(const struct sieve_action_exec_env *aenv,
+			    const void *id, size_t id_size, time_t time)
+{
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+
+	return sieve_execute_duplicate_mark(eenv, id, id_size, time);
+}
 
-bool sieve_action_duplicate_check_available(
-	const struct sieve_script_env *senv);
-bool sieve_action_duplicate_check(const struct sieve_script_env *senv,
-				  const void *id, size_t id_size);
-void sieve_action_duplicate_mark(const struct sieve_script_env *senv,
-				 const void *id, size_t id_size, time_t time);
-void sieve_action_duplicate_flush(const struct sieve_script_env *senv);
+/*
+ * Action utility functions
+ */
 
 /* Rejecting mail */
 
diff --git a/src/lib-sieve/sieve-execute.c b/src/lib-sieve/sieve-execute.c
index 6b7b1cf99c225f126f754a8dc9a39933e0bb4558..a395cc6becff05b5de95fa17a06f09150ee40176 100644
--- a/src/lib-sieve/sieve-execute.c
+++ b/src/lib-sieve/sieve-execute.c
@@ -5,11 +5,34 @@
 
 #include "sieve-execute.h"
 
+struct sieve_execute_state {
+	void *dup_trans;
+};
+
 struct event_category event_category_sieve_execute = {
 	.parent = &event_category_sieve,
 	.name = "sieve-execute",
 };
 
+static struct sieve_execute_state *
+sieve_execute_state_create(struct sieve_execute_env *eenv)
+{
+	return p_new(eenv->pool, struct sieve_execute_state, 1);
+}
+
+static void
+sieve_execute_state_free(struct sieve_execute_state **_estate,
+			 struct sieve_execute_env *eenv)
+{
+	struct sieve_execute_state *estate = *_estate;
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	*_estate = NULL;
+
+	if (senv->duplicate_transaction_rollback != NULL)
+		senv->duplicate_transaction_rollback(&estate->dup_trans);
+}
+
 void sieve_execute_init(struct sieve_execute_env *eenv,
 			struct sieve_instance *svinst, pool_t pool,
 			const struct sieve_message_data *msgdata,
@@ -35,6 +58,8 @@ void sieve_execute_init(struct sieve_execute_env *eenv,
 			smtp_address_encode(msgdata->envelope.rcpt_to));
 	}
 
+	eenv->state = sieve_execute_state_create(eenv);
+
 	eenv->exec_status = senv->exec_status;
 	if (eenv->exec_status == NULL)
 		eenv->exec_status = p_new(pool, struct sieve_exec_status, 1);
@@ -42,9 +67,96 @@ void sieve_execute_init(struct sieve_execute_env *eenv,
 		i_zero(eenv->exec_status);
 }
 
+void sieve_execute_finish(struct sieve_execute_env *eenv, int status)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	if (status == SIEVE_EXEC_OK) {
+		if (senv->duplicate_transaction_commit != NULL) {
+			senv->duplicate_transaction_commit(
+				&eenv->state->dup_trans);
+		}
+	} else {
+		if (senv->duplicate_transaction_rollback != NULL) {
+			senv->duplicate_transaction_rollback(
+				&eenv->state->dup_trans);
+		}
+	}
+}
+
 void sieve_execute_deinit(struct sieve_execute_env *eenv)
 {
+	sieve_execute_state_free(&eenv->state, eenv);
 	event_unref(&eenv->event);
 	pool_unref(&eenv->pool);
 }
 
+/*
+ * Checking for duplicates
+ */
+
+static void *
+sieve_execute_get_dup_transaction(const struct sieve_execute_env *eenv)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	if (senv->duplicate_transaction_begin == NULL)
+		return NULL;
+	if (eenv->state->dup_trans == NULL) {
+		eenv->state->dup_trans =
+			senv->duplicate_transaction_begin(senv);
+	}
+	return eenv->state->dup_trans;
+}
+
+bool sieve_execute_duplicate_check_available(
+	const struct sieve_execute_env *eenv)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	return (senv->duplicate_transaction_begin != NULL);
+}
+
+int sieve_execute_duplicate_check(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size,
+				  bool *duplicate_r)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+	void *dup_trans = sieve_execute_get_dup_transaction(eenv);
+	int ret;
+
+	*duplicate_r = FALSE;
+
+	if (senv->duplicate_check == NULL)
+		return SIEVE_EXEC_OK;
+
+	e_debug(eenv->svinst->event, "Check duplicate ID");
+
+	ret = senv->duplicate_check(dup_trans, senv, id, id_size);
+	switch (ret) {
+	case SIEVE_DUPLICATE_CHECK_RESULT_EXISTS:
+		*duplicate_r = TRUE;
+		break;
+	case SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		break;
+	case SIEVE_DUPLICATE_CHECK_RESULT_FAILURE:
+		return SIEVE_EXEC_FAILURE;
+	case SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE:
+		return SIEVE_EXEC_TEMP_FAILURE;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+void sieve_execute_duplicate_mark(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size, time_t time)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+	void *dup_trans = sieve_execute_get_dup_transaction(eenv);
+
+	if (senv->duplicate_mark == NULL)
+		return;
+
+	e_debug(eenv->svinst->event, "Mark ID as duplicate");
+
+	senv->duplicate_mark(dup_trans, senv, id, id_size, time);
+}
diff --git a/src/lib-sieve/sieve-execute.h b/src/lib-sieve/sieve-execute.h
index cff050462914f4953d8329b4801217e7dafe396a..8af182b43061b4b1cfe10276145c041f65584f6d 100644
--- a/src/lib-sieve/sieve-execute.h
+++ b/src/lib-sieve/sieve-execute.h
@@ -3,6 +3,8 @@
 
 #include "sieve-common.h"
 
+struct sieve_execute_state;
+
 struct sieve_execute_env {
 	struct sieve_instance *svinst;
 	pool_t pool;
@@ -13,6 +15,7 @@ struct sieve_execute_env {
 	const struct sieve_message_data *msgdata;
 	const struct sieve_script_env *scriptenv;
 
+	struct sieve_execute_state *state;
 	struct sieve_exec_status *exec_status;
 };
 
@@ -21,6 +24,19 @@ void sieve_execute_init(struct sieve_execute_env *eenv,
 			const struct sieve_message_data *msgdata,
 			const struct sieve_script_env *senv,
 			enum sieve_execute_flags flags);
+void sieve_execute_finish(struct sieve_execute_env *eenv, int status);
 void sieve_execute_deinit(struct sieve_execute_env *eenv);
 
+/*
+ * Checking for duplicates
+ */
+
+bool sieve_execute_duplicate_check_available(
+	const struct sieve_execute_env *eenv);
+int sieve_execute_duplicate_check(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size,
+				  bool *duplicate_r);
+void sieve_execute_duplicate_mark(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size, time_t time);
+
 #endif
diff --git a/src/lib-sieve/sieve-result.c b/src/lib-sieve/sieve-result.c
index effd6f28e823c9e8dbf8144d71b337cd3db0a8ce..447ccee7de22b090dd200e2b477db9acee55ea3b 100644
--- a/src/lib-sieve/sieve-result.c
+++ b/src/lib-sieve/sieve-result.c
@@ -930,7 +930,6 @@ struct sieve_result_execution {
 	struct sieve_action_execution *keep_equiv_action;
 	int keep_status;
 
-	bool dup_flushed:1;
 	bool keep_success:1;
 	bool keep_explicit:1;
 	bool keep_implicit:1;
@@ -1076,9 +1075,6 @@ static int
 sieve_result_action_start(struct sieve_result_execution *rexec,
 			  struct sieve_action_execution *aexec)
 {
-	const struct sieve_action_exec_env *aenv = &rexec->action_env;
-	const struct sieve_execute_env *eenv = aenv->exec_env;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct sieve_result_action *rac = aexec->action;
 	struct sieve_action *act = &rac->action;
 	int status = SIEVE_EXEC_OK;
@@ -1093,12 +1089,6 @@ sieve_result_action_start(struct sieve_result_execution *rexec,
 	if (act->def == NULL)
 		return status;
 
-	if ((act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) != 0 &&
-	    !rexec->dup_flushed) {
-		sieve_action_duplicate_flush(senv);
-		rexec->dup_flushed = TRUE;
-	}
-
 	if (act->def->start != NULL) {
 		sieve_action_execution_pre(rexec, aexec);
 		status = act->def->start(&rexec->action_env,
@@ -1753,7 +1743,6 @@ static int sieve_result_transaction_start(struct sieve_result_execution *rexec)
 
 	e_debug(rexec->event, "Starting execution of actions");
 
-	rexec->dup_flushed = FALSE;
 	aexec = rexec->actions_head;
 	while (status == SIEVE_EXEC_OK && aexec != NULL) {
 		status = sieve_result_action_start(rexec, aexec);
diff --git a/src/lib-sieve/sieve-result.h b/src/lib-sieve/sieve-result.h
index ed51e967a8eef0a162dcca5a8ef22472a84844e2..a9fcadc7a5e6ca6f123ec2e09e6faac69d758813 100644
--- a/src/lib-sieve/sieve-result.h
+++ b/src/lib-sieve/sieve-result.h
@@ -110,6 +110,9 @@ struct sieve_result_execution *
 sieve_result_execution_create(struct sieve_result *result, pool_t pool);
 void sieve_result_execution_destroy(struct sieve_result_execution **_rexec);
 
+void *sieve_result_execution_get_dup_transaction(
+	struct sieve_result_execution *rexec);
+
 int sieve_result_execute(struct sieve_result_execution *rexec, int status,
 			 bool commit, struct sieve_error_handler *ehandler,
 			 bool *keep_r);
diff --git a/src/lib-sieve/sieve-types.h b/src/lib-sieve/sieve-types.h
index f2e64a4cda3c0786f87c8420c354e99079666e10..439b26ed7c46cd45b0cdb71f71586ec2ce6184ac 100644
--- a/src/lib-sieve/sieve-types.h
+++ b/src/lib-sieve/sieve-types.h
@@ -188,6 +188,17 @@ struct sieve_trace_config {
 	unsigned int flags;
 };
 
+/*
+ * Duplicate checking
+ */
+
+enum sieve_duplicate_check_result {
+	SIEVE_DUPLICATE_CHECK_RESULT_EXISTS = 1,
+	SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND = 0,
+	SIEVE_DUPLICATE_CHECK_RESULT_FAILURE = -1,
+	SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE = -2,
+};
+
 /*
  * Script environment
  *
@@ -229,13 +240,17 @@ struct sieve_script_env {
 			const char **error_r);
 
 	/* Interface for marking and checking duplicates */
-	bool (*duplicate_check)
-		(const struct sieve_script_env *senv, const void *id, size_t id_size);
-	void (*duplicate_mark)
-		(const struct sieve_script_env *senv, const void *id, size_t id_size,
-			time_t time);
-	void (*duplicate_flush)
-		(const struct sieve_script_env *senv);
+	void *(*duplicate_transaction_begin)(
+		const struct sieve_script_env *senv);
+	void (*duplicate_transaction_commit)(void **_dup_trans);
+	void (*duplicate_transaction_rollback)(void **_dup_trans);
+
+	enum sieve_duplicate_check_result
+	(*duplicate_check)(void *dup_trans, const struct sieve_script_env *senv,
+			   const void *id, size_t id_size);
+	void (*duplicate_mark)(void *dup_trans,
+			       const struct sieve_script_env *senv,
+			       const void *id, size_t id_size, time_t time);
 
 	/* Interface for rejecting mail */
 	int (*reject_mail)(const struct sieve_script_env *senv,
diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c
index 59ea41766b250449e92a2ddcdea0ac693f2a5342..69827a93765003a6e5eed4b1851a21fef8b6e327 100644
--- a/src/lib-sieve/sieve.c
+++ b/src/lib-sieve/sieve.c
@@ -671,6 +671,7 @@ int sieve_execute(struct sieve_binary *sbin,
 	/* Cleanup */
 	if (result != NULL)
 		sieve_result_unref(&result);
+	sieve_execute_finish(&eenv, ret);
 	sieve_execute_deinit(&eenv);
 	pool_unref(&pool);
 
@@ -934,6 +935,8 @@ int sieve_multiscript_finish(struct sieve_multiscript **_mscript,
 		sieve_execution_exitcode_to_str(status),
 		(mscript->keep ? "yes" : "no"));
 
+	sieve_execute_finish(&mscript->exec_env, status);
+
 	/* Cleanup */
 	sieve_multiscript_destroy(&mscript);
 
diff --git a/src/plugins/imap-filter-sieve/imap-filter-sieve.c b/src/plugins/imap-filter-sieve/imap-filter-sieve.c
index fe6e4a81e128fcbddab3e85ff5f3bdd78a8a88d6..62519f4c70ccf52a3d3f8cb3e394aaa4b7ce80a0 100644
--- a/src/plugins/imap-filter-sieve/imap-filter-sieve.c
+++ b/src/plugins/imap-filter-sieve/imap-filter-sieve.c
@@ -627,38 +627,66 @@ imap_filter_sieve_smtp_finish(const struct sieve_script_env *senv ATTR_UNUSED,
  * Duplicate checking
  */
 
-static bool
-imap_filter_sieve_duplicate_check(const struct sieve_script_env *senv,
-				  const void *id, size_t id_size)
+static void *
+imap_filter_sieve_duplicate_transaction_begin(
+	const struct sieve_script_env *senv)
 {
 	struct imap_filter_sieve_context *sctx = senv->script_context;
 	struct imap_filter_sieve_user *ifsuser =
 		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
 
-	return mail_duplicate_check(ifsuser->dup_db,
-		id, id_size, senv->user->username);
+	return mail_duplicate_transaction_begin(ifsuser->dup_db);
 }
 
-static void
-imap_filter_sieve_duplicate_mark(const struct sieve_script_env *senv,
-				 const void *id, size_t id_size, time_t time)
+static void imap_filter_sieve_duplicate_transaction_commit(void **_dup_trans)
 {
-	struct imap_filter_sieve_context *sctx = senv->script_context;
-	struct imap_filter_sieve_user *ifsuser =
-		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
 
-	mail_duplicate_mark(ifsuser->dup_db,
-		id, id_size, senv->user->username, time);
+	mail_duplicate_transaction_commit(&dup_trans);
+}
+
+static void imap_filter_sieve_duplicate_transaction_rollback(void **_dup_trans)
+{
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
+
+	mail_duplicate_transaction_rollback(&dup_trans);
+}
+
+static enum sieve_duplicate_check_result
+imap_filter_sieve_duplicate_check(void *_dup_trans,
+				  const struct sieve_script_env *senv,
+				  const void *id, size_t id_size)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
+
+	switch (mail_duplicate_check(dup_trans, id, id_size,
+				     senv->user->username)) {
+	case MAIL_DUPLICATE_CHECK_RESULT_EXISTS:
+		return SIEVE_DUPLICATE_CHECK_RESULT_EXISTS;
+	case MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		return SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND;
+	case MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK:
+	case MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT:
+		return SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE;
+	case MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR:
+	case MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS:
+		break;
+	}
+	return SIEVE_DUPLICATE_CHECK_RESULT_FAILURE;
 }
 
 static void
-imap_filter_sieve_duplicate_flush(const struct sieve_script_env *senv)
+imap_filter_sieve_duplicate_mark(void *_dup_trans,
+				 const struct sieve_script_env *senv,
+				 const void *id, size_t id_size, time_t time)
 {
-	struct imap_filter_sieve_context *sctx = senv->script_context;
-	struct imap_filter_sieve_user *ifsuser =
-		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
 
-	mail_duplicate_db_flush(ifsuser->dup_db);
+	mail_duplicate_mark(dup_trans, id, id_size, senv->user->username, time);
 }
 
 /*
@@ -943,9 +971,14 @@ int imap_sieve_filter_run_init(struct imap_filter_sieve_context *sctx)
 	scriptenv->smtp_send = imap_filter_sieve_smtp_send;
 	scriptenv->smtp_abort = imap_filter_sieve_smtp_abort;
 	scriptenv->smtp_finish = imap_filter_sieve_smtp_finish;
+	scriptenv->duplicate_transaction_begin =
+		imap_filter_sieve_duplicate_transaction_begin;
+	scriptenv->duplicate_transaction_commit =
+		imap_filter_sieve_duplicate_transaction_commit;
+	scriptenv->duplicate_transaction_rollback =
+		imap_filter_sieve_duplicate_transaction_rollback;
 	scriptenv->duplicate_mark = imap_filter_sieve_duplicate_mark;
 	scriptenv->duplicate_check = imap_filter_sieve_duplicate_check;
-	scriptenv->duplicate_flush = imap_filter_sieve_duplicate_flush;
 	scriptenv->script_context = sctx;
 	return 0;
 }
diff --git a/src/plugins/imapsieve/imap-sieve.c b/src/plugins/imapsieve/imap-sieve.c
index e5b08947bda1c785c541c6d1fce8f64574fdaeba..e6cc24a51fb569661261c56e485f9b93264e3f28 100644
--- a/src/plugins/imapsieve/imap-sieve.c
+++ b/src/plugins/imapsieve/imap-sieve.c
@@ -219,32 +219,60 @@ imap_sieve_smtp_finish(const struct sieve_script_env *senv ATTR_UNUSED,
  * Duplicate checking
  */
 
-static bool
-imap_sieve_duplicate_check(const struct sieve_script_env *senv, const void *id,
-			   size_t id_size)
+static void *
+imap_sieve_duplicate_transaction_begin(const struct sieve_script_env *senv)
 {
 	struct imap_sieve_context *isctx = senv->script_context;
 
-	return mail_duplicate_check(isctx->isieve->dup_db, id, id_size,
-				    senv->user->username);
+	return mail_duplicate_transaction_begin(isctx->isieve->dup_db);
 }
 
-static void
-imap_sieve_duplicate_mark(const struct sieve_script_env *senv, const void *id,
-			  size_t id_size, time_t time)
+static void imap_sieve_duplicate_transaction_commit(void **_dup_trans)
 {
-	struct imap_sieve_context *isctx = senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_commit(&dup_trans);
+}
+
+static void imap_sieve_duplicate_transaction_rollback(void **_dup_trans)
+{
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
 
-	mail_duplicate_mark(isctx->isieve->dup_db, id, id_size,
-			    senv->user->username, time);
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_rollback(&dup_trans);
+}
+
+static enum sieve_duplicate_check_result
+imap_sieve_duplicate_check(void *_dup_trans,
+			   const struct sieve_script_env *senv,
+			   const void *id, size_t id_size)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
+
+	switch (mail_duplicate_check(dup_trans, id, id_size,
+				     senv->user->username)) {
+	case MAIL_DUPLICATE_CHECK_RESULT_EXISTS:
+		return SIEVE_DUPLICATE_CHECK_RESULT_EXISTS;
+	case MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		return SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND;
+	case MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK:
+	case MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT:
+		return SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE;
+	case MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR:
+	case MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS:
+		break;
+	}
+	return SIEVE_DUPLICATE_CHECK_RESULT_FAILURE;
 }
 
 static void
-imap_sieve_duplicate_flush(const struct sieve_script_env *senv)
+imap_sieve_duplicate_mark(void *_dup_trans, const struct sieve_script_env *senv,
+			  const void *id, size_t id_size, time_t time)
 {
-	struct imap_sieve_context *isctx = senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
 
-	mail_duplicate_db_flush(isctx->isieve->dup_db);
+	mail_duplicate_mark(dup_trans, id, id_size, senv->user->username, time);
 }
 
 /*
@@ -884,9 +912,14 @@ int imap_sieve_run_mail(struct imap_sieve_run *isrun, struct mail *mail,
 			scriptenv.smtp_send = imap_sieve_smtp_send;
 			scriptenv.smtp_abort = imap_sieve_smtp_abort;
 			scriptenv.smtp_finish = imap_sieve_smtp_finish;
+			scriptenv.duplicate_transaction_begin =
+				imap_sieve_duplicate_transaction_begin;
+			scriptenv.duplicate_transaction_commit =
+				imap_sieve_duplicate_transaction_commit;
+			scriptenv.duplicate_transaction_rollback =
+				imap_sieve_duplicate_transaction_rollback;
 			scriptenv.duplicate_mark = imap_sieve_duplicate_mark;
 			scriptenv.duplicate_check = imap_sieve_duplicate_check;
-			scriptenv.duplicate_flush = imap_sieve_duplicate_flush;
 			scriptenv.result_amend_log_message =
 				imap_sieve_result_amend_log_message;
 			scriptenv.trace_log = trace_log;
diff --git a/src/plugins/lda-sieve/lda-sieve-plugin.c b/src/plugins/lda-sieve/lda-sieve-plugin.c
index 60b3b18856921d4fd31e02641348763d550f05d0..4e79c1f1b58b711acd868a47eb2c0e7402ab6344 100644
--- a/src/plugins/lda-sieve/lda-sieve-plugin.c
+++ b/src/plugins/lda-sieve/lda-sieve-plugin.c
@@ -148,34 +148,60 @@ lda_sieve_reject_mail(const struct sieve_script_env *senv,
  * Duplicate checking
  */
 
-static bool
-lda_sieve_duplicate_check(const struct sieve_script_env *senv,
-			  const void *id, size_t id_size)
+static void *
+lda_sieve_duplicate_transaction_begin(const struct sieve_script_env *senv)
 {
 	struct mail_deliver_context *dctx =
 		(struct mail_deliver_context *)senv->script_context;
 
-	return mail_duplicate_check(dctx->dup_db, id, id_size,
-				    senv->user->username);
+	return mail_duplicate_transaction_begin(dctx->dup_db);
 }
 
-static void
-lda_sieve_duplicate_mark(const struct sieve_script_env *senv, const void *id,
-			 size_t id_size, time_t time)
+static void lda_sieve_duplicate_transaction_commit(void **_dup_trans)
 {
-	struct mail_deliver_context *dctx =
-		(struct mail_deliver_context *)senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
 
-	mail_duplicate_mark(dctx->dup_db,
-		id, id_size, senv->user->username, time);
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_commit(&dup_trans);
 }
 
-static void lda_sieve_duplicate_flush(const struct sieve_script_env *senv)
+static void lda_sieve_duplicate_transaction_rollback(void **_dup_trans)
 {
-	struct mail_deliver_context *dctx =
-		(struct mail_deliver_context *)senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_rollback(&dup_trans);
+}
+
+static enum sieve_duplicate_check_result
+lda_sieve_duplicate_check(void *_dup_trans, const struct sieve_script_env *senv,
+			  const void *id, size_t id_size)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
+
+	switch (mail_duplicate_check(dup_trans, id, id_size,
+				     senv->user->username)) {
+	case MAIL_DUPLICATE_CHECK_RESULT_EXISTS:
+		return SIEVE_DUPLICATE_CHECK_RESULT_EXISTS;
+	case MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		return SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND;
+	case MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK:
+	case MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT:
+		return SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE;
+	case MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR:
+	case MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS:
+		break;
+	}
+	return SIEVE_DUPLICATE_CHECK_RESULT_FAILURE;
+}
+
+static void
+lda_sieve_duplicate_mark(void *_dup_trans, const struct sieve_script_env *senv,
+			 const void *id, size_t id_size, time_t time)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
 
-	mail_duplicate_db_flush(dctx->dup_db);
+	mail_duplicate_mark(dup_trans, id, id_size, senv->user->username, time);
 }
 
 /*
@@ -984,9 +1010,14 @@ lda_sieve_execute(struct lda_sieve_run_context *srctx,
 	scriptenv.smtp_send = lda_sieve_smtp_send;
 	scriptenv.smtp_abort = lda_sieve_smtp_abort;
 	scriptenv.smtp_finish = lda_sieve_smtp_finish;
+	scriptenv.duplicate_transaction_begin =
+		lda_sieve_duplicate_transaction_begin;
+	scriptenv.duplicate_transaction_commit =
+		lda_sieve_duplicate_transaction_commit;
+	scriptenv.duplicate_transaction_rollback =
+		lda_sieve_duplicate_transaction_rollback;
 	scriptenv.duplicate_mark = lda_sieve_duplicate_mark;
 	scriptenv.duplicate_check = lda_sieve_duplicate_check;
-	scriptenv.duplicate_flush = lda_sieve_duplicate_flush;
 	scriptenv.reject_mail = lda_sieve_reject_mail;
 	scriptenv.result_amend_log_message = lda_sieve_result_amend_log_message;
 	scriptenv.script_context = (void *) mdctx;
diff --git a/src/sieve-tools/sieve-test.c b/src/sieve-tools/sieve-test.c
index 5a4235c099a9861643b3a8a94fd4ff569a246a04..e7345be886393d84ac0e139c4ca2a569e86523f6 100644
--- a/src/sieve-tools/sieve-test.c
+++ b/src/sieve-tools/sieve-test.c
@@ -107,17 +107,34 @@ sieve_smtp_finish(const struct sieve_script_env *senv ATTR_UNUSED, void *handle,
  * Dummy duplicate check implementation
  */
 
-static bool
-duplicate_check(const struct sieve_script_env *senv, const void *id ATTR_UNUSED,
-		size_t id_size ATTR_UNUSED)
+static void *
+duplicate_transaction_begin(const struct sieve_script_env *senv ATTR_UNUSED)
+{
+	return NULL;
+}
+
+static void duplicate_transaction_commit(void **_dup_trans ATTR_UNUSED)
+{
+}
+
+static void duplicate_transaction_rollback(void **_dup_trans ATTR_UNUSED)
+{
+}
+
+static int
+duplicate_check(void *_dup_trans ATTR_UNUSED,
+		const struct sieve_script_env *senv,
+		const void *id ATTR_UNUSED, size_t id_size ATTR_UNUSED)
 {
 	i_info("checked duplicate for user %s.\n", senv->user->username);
 	return 0;
 }
 
 static void
-duplicate_mark(const struct sieve_script_env *senv, const void *id ATTR_UNUSED,
-	       size_t id_size ATTR_UNUSED, time_t time ATTR_UNUSED)
+duplicate_mark(void *_dup_trans ATTR_UNUSED,
+	       const struct sieve_script_env *senv,
+	       const void *id ATTR_UNUSED, size_t id_size ATTR_UNUSED,
+	       time_t time ATTR_UNUSED)
 {
 	i_info("marked duplicate for user %s.\n", senv->user->username);
 }
@@ -342,6 +359,12 @@ int main(int argc, char **argv)
 		scriptenv.smtp_send = sieve_smtp_send;
 		scriptenv.smtp_abort = sieve_smtp_abort;
 		scriptenv.smtp_finish = sieve_smtp_finish;
+		scriptenv.duplicate_transaction_begin =
+			duplicate_transaction_begin;
+		scriptenv.duplicate_transaction_commit =
+			duplicate_transaction_commit;
+		scriptenv.duplicate_transaction_rollback =
+			duplicate_transaction_rollback;
 		scriptenv.duplicate_mark = duplicate_mark;
 		scriptenv.duplicate_check = duplicate_check;
 		scriptenv.result_amend_log_message = result_amend_log_message;
diff --git a/src/testsuite/testsuite-script.c b/src/testsuite/testsuite-script.c
index 7c89ecd24374cb06443d75bcd787a9d7985fd755..b6edd9b14356c9383d9b712696a7aaec5af5fc13 100644
--- a/src/testsuite/testsuite-script.c
+++ b/src/testsuite/testsuite-script.c
@@ -147,8 +147,9 @@ bool testsuite_script_run(const struct sieve_runtime_env *renv)
 	}
 
 	ret = sieve_interpreter_run(interp, result);
-
 	sieve_interpreter_free(&interp);
+
+	sieve_execute_finish(&exec_env, ret);
 	sieve_execute_deinit(&exec_env);
 
 	return (ret > 0 ||