diff --git a/doc/plugins/imapsieve.txt b/doc/plugins/imapsieve.txt
index 400fc123e5117194e0266ddce849b77ab4584013..1ab1d9ccf9b2f8bbeba667cf9004413f30c08369 100644
--- a/doc/plugins/imapsieve.txt
+++ b/doc/plugins/imapsieve.txt
@@ -41,7 +41,11 @@ vnd.dovecot.mailbox-from
 
 vnd.dovecot.mailbox-to
   The mailbox where the message is being copied to (the destination mailbox).
-  This environment item is currently always equal to the "imap.mailbox" item.
+  If the Sieve script is not executed from an IMAP COPY or MOVE command, this
+  environment is always equal to the "imap.mailbox" environment item. Otherwise,
+  the "imap.mailbox" item points to the mailbox where the Sieve script is
+  executing for, which may actually be the source mailbox (see the
+  imapsieve_mailboxXXX_copy_source_after setting).
 
 Configuration
 =============
@@ -111,6 +115,13 @@ imapsieve_mailboxXXX_from =
   mailbox. This setting supports wildcards with a syntax compatible with the
   IMAP LIST command
 
+imapsieve_mailboxXXX_copy_source_after =
+  When the cause is "COPY", run the specified Sieve script for the message in
+  the source mailbox after the Sieve script for the corresponding message in the
+  destination mailbox successfully finishes executing . This does not apply to
+  moved messages, since the message is removed from the source mailbox in that
+  case.
+
 Example
 -------
 
diff --git a/src/plugins/imapsieve/imap-sieve-storage.c b/src/plugins/imapsieve/imap-sieve-storage.c
index d60b841d62f3bd9d01420fde7813761cded425d2..01d32e8144ecbb9f69df270e46327fb04e36f0de 100644
--- a/src/plugins/imapsieve/imap-sieve-storage.c
+++ b/src/plugins/imapsieve/imap-sieve-storage.c
@@ -57,6 +57,7 @@ struct imap_sieve_mailbox_rule {
 	const char *from;
 	const char *const *causes;
 	const char *before, *after;
+	const char *copy_source_after;
 };
 
 struct imap_sieve_user {
@@ -74,7 +75,7 @@ struct imap_sieve_user {
 };
 
 struct imap_sieve_mailbox_event {
-	uint32_t mail_uid;
+	uint32_t dest_mail_uid, src_mail_uid;
 	unsigned int save_seq;
 
 	const char *changed_flags;
@@ -84,7 +85,9 @@ struct imap_sieve_mailbox_transaction {
 	pool_t pool;
 
 	union mailbox_transaction_module_context module_ctx;
+
 	struct mailbox *src_box;
+	struct mailbox_transaction_context *src_mail_trans;
 
 	ARRAY_TYPE(imap_sieve_mailbox_event) events;
 };
@@ -162,6 +165,22 @@ imap_sieve_mailbox_debug(struct mailbox *box,
 	}
 }
 
+static inline void
+imap_sieve_mailbox_warning(struct mailbox *box,
+	const char *format, ...) ATTR_FORMAT(2, 3);
+static inline void
+imap_sieve_mailbox_warning(struct mailbox *box,
+	const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	i_warning("imapsieve: mailbox %s: %s",
+		mailbox_get_vname(box),
+		t_strdup_vprintf(format, args));
+	va_end(args);
+}
+
 static inline void
 imap_sieve_mailbox_error(struct mailbox *box,
 	const char *format, ...) ATTR_FORMAT(2, 3);
@@ -263,9 +282,25 @@ static int imap_sieve_mailbox_get_script
 	return ret;
 }
 
+static struct imap_sieve_mailbox_event *
+imap_sieve_create_mailbox_event
+(struct mailbox_transaction_context *t, struct mail *dest_mail)
+{
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t);
+	struct imap_sieve_mailbox_event *event;
+
+	if (!array_is_created(&ismt->events))
+		i_array_init(&ismt->events, 64);
+
+	event = array_append_space(&ismt->events);
+	event->save_seq = t->save_count;
+	event->dest_mail_uid = dest_mail->uid;
+	return event;
+}
+
 static void imap_sieve_add_mailbox_event
 (struct mailbox_transaction_context *t,
-	struct mail *mail, struct mailbox *src_box,
+	struct mail *dest_mail, struct mailbox *src_box,
 	const char *changed_flags)
 {
 	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t);
@@ -274,15 +309,28 @@ static void imap_sieve_add_mailbox_event
 	i_assert(ismt->src_box == NULL || ismt->src_box == src_box);
 	ismt->src_box = src_box;
 
-	if (!array_is_created(&ismt->events))
-		i_array_init(&ismt->events, 64);
-
-	event = array_append_space(&ismt->events);
-	event->save_seq = t->save_count;
-	event->mail_uid = mail->uid;
+	event = imap_sieve_create_mailbox_event(t, dest_mail);
 	event->changed_flags = p_strdup(ismt->pool, changed_flags);
 }
 
+static void imap_sieve_add_mailbox_copy_event
+(struct mailbox_transaction_context *t,
+	struct mail *dest_mail, struct mail *src_mail)
+{
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t);
+	struct imap_sieve_mailbox_event *event;
+
+	i_assert(ismt->src_box == NULL || ismt->src_box == src_mail->box);
+	i_assert(ismt->src_mail_trans == NULL ||
+		ismt->src_mail_trans == src_mail->transaction);
+
+	ismt->src_box = src_mail->box;
+	ismt->src_mail_trans = src_mail->transaction;
+
+	event = imap_sieve_create_mailbox_event(t, dest_mail);
+	event->src_mail_uid = src_mail->uid;
+}
+
 /*
  * Mail
  */
@@ -436,8 +484,7 @@ imap_sieve_mailbox_copy(struct mail_save_context *ctx, struct mail *mail)
 		imap_sieve_mailbox_debug(t->box, "%s event",
 			(isuser->cur_cmd == IMAP_SIEVE_CMD_COPY ?
 				"COPY" : "MOVE"));
-		imap_sieve_add_mailbox_event
-			(t, ctx->dest_mail, mail->box, NULL);
+		imap_sieve_add_mailbox_copy_event(t, ctx->dest_mail, mail);
 	}
 
 	return 0;
@@ -508,6 +555,44 @@ imap_sieve_mailbox_transaction_free
 	pool_unref(&ismt->pool);
 }
 
+static void
+imap_sieve_mailbox_run_copy_source(
+	struct imap_sieve_mailbox_transaction *ismt,
+	struct imap_sieve_run *isrun,
+	const struct imap_sieve_mailbox_event *mevent,
+	struct mail **src_mail)
+{
+	struct mailbox *src_box = ismt->src_box;
+	int ret;
+
+	if (isrun == NULL)
+		return;
+
+	i_assert(ismt->src_mail_trans->box == src_box);
+
+	if (*src_mail == NULL)
+		*src_mail = mail_alloc(ismt->src_mail_trans, 0, NULL);
+
+	/* Select source message */
+	if (!mail_set_uid(*src_mail, mevent->src_mail_uid)) {
+		imap_sieve_mailbox_warning(src_box,
+			"Failed to find source message for Sieve event "
+			"(UID=%llu)", (unsigned long long)mevent->src_mail_uid);
+		return;
+	}
+
+	imap_sieve_mailbox_debug(src_box,
+		"Running copy_source_after scripts.");
+
+	/* Run scripts for source mail */
+	ret = imap_sieve_run_mail
+		(isrun, *src_mail, NULL);
+	if (ret > 0) {
+		/* Discard */
+		mail_update_flags(*src_mail, MODIFY_ADD, MAIL_DELETED);
+	}
+}
+
 static int
 imap_sieve_mailbox_transaction_run(
 	struct imap_sieve_mailbox_transaction *ismt,
@@ -525,11 +610,11 @@ imap_sieve_mailbox_transaction_run(
 	struct mailbox_header_lookup_ctx *headers_ctx;
 	struct mailbox_transaction_context *st;
 	struct mailbox *sbox;
-	struct imap_sieve_run *isrun;
+	struct imap_sieve_run *isrun, *isrun_src;
 	struct seq_range_iter siter;
 	const char *cause, *script_name = NULL;
 	bool can_discard;
-	struct mail *mail;
+	struct mail *mail, *src_mail = NULL;
 	int ret;
 
 	if (ismt == NULL || !array_is_created(&ismt->events)) {
@@ -574,6 +659,7 @@ imap_sieve_mailbox_transaction_run(
 	T_BEGIN {
 		ARRAY_TYPE(imap_sieve_mailbox_rule) mbrules;
 		ARRAY_TYPE(const_string) scripts_before, scripts_after;
+		ARRAY_TYPE(const_string) scripts_copy_source;
 		struct imap_sieve_mailbox_rule *const *rule_idx;
 
 		/* Find matching rules */
@@ -584,6 +670,7 @@ imap_sieve_mailbox_transaction_run(
 		/* Apply all matched rules */
 		t_array_init(&scripts_before, 8);
 		t_array_init(&scripts_after, 8);
+		t_array_init(&scripts_copy_source, 4);
 		array_foreach(&mbrules, rule_idx) {
 			struct imap_sieve_mailbox_rule *rule = *rule_idx;
 
@@ -591,6 +678,8 @@ imap_sieve_mailbox_transaction_run(
 				array_append(&scripts_before, &rule->before, 1);
 			if (rule->after != NULL)
 				array_append(&scripts_after, &rule->after, 1);
+			if (rule->copy_source_after != NULL)
+				array_append(&scripts_copy_source, &rule->copy_source_after, 1);
 		}
 		(void)array_append_space(&scripts_before);
 		(void)array_append_space(&scripts_after);
@@ -600,6 +689,21 @@ imap_sieve_mailbox_transaction_run(
 			(isuser->isieve, dest_box, src_box, cause, script_name,
 				array_idx(&scripts_before, 0),
 				array_idx(&scripts_after, 0), &isrun);
+
+		/* Initialize source script execution */
+		isrun_src = NULL;
+		if (ret > 0 && ismt->src_mail_trans != NULL &&
+			isuser->cur_cmd == IMAP_SIEVE_CMD_COPY &&
+			array_count(&scripts_copy_source) > 0) {
+			const char *no_scripts = NULL;
+
+			(void)array_append_space(&scripts_copy_source);
+			if (imap_sieve_run_init(isuser->isieve,
+					dest_box, src_box, cause, NULL,
+					&no_scripts, array_idx(&scripts_copy_source, 0),
+					&isrun_src) <= 0)
+				isrun_src = NULL;
+		}
 	} T_END;
 
 	if (ret <= 0) {
@@ -607,11 +711,13 @@ imap_sieve_mailbox_transaction_run(
 		return 0;
 	}
 
-	/* Get synchronized view on the mailbox */
+	/* Get synchronized view on the destination mailbox */
 	sbox = mailbox_alloc(dest_box->list, dest_box->vname, 0);
 	if (mailbox_sync(sbox, 0) < 0) {
 		mailbox_free(&sbox);
 		imap_sieve_run_deinit(&isrun);
+		if (isrun_src != NULL)
+			imap_sieve_run_deinit(&isrun_src);
 		return -1;
 	}
 
@@ -627,9 +733,9 @@ imap_sieve_mailbox_transaction_run(
 		uint32_t uid;
 
 		/* Determine UID for saved message */
-		if (mevent->mail_uid > 0 ||
+		if (mevent->dest_mail_uid > 0 ||
 			!seq_range_array_iter_nth(&siter, mevent->save_seq, &uid))
-			uid = mevent->mail_uid;
+			uid = mevent->dest_mail_uid;
 
 		/* Select event message */
 		if (!mail_set_uid(mail, uid)) {
@@ -648,16 +754,25 @@ imap_sieve_mailbox_transaction_run(
 		/* Handle the result */
 		if (ret < 0) {
 			/* Sieve error; keep */
-		} else if (ret > 0 && can_discard) {
-			/* Discard */
-			mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED);
+		} else {
+			if (ret > 0 && can_discard) {
+				/* Discard */
+				mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED);
+			}
+
+			imap_sieve_mailbox_run_copy_source
+				(ismt, isrun_src, mevent, &src_mail);
 		}
 	}
 
 	/* Cleanup */
 	mail_free(&mail);
 	ret = mailbox_transaction_commit(&st);
+	if (src_mail != NULL)
+		mail_free(&src_mail);
 	imap_sieve_run_deinit(&isrun);
+	if (isrun_src != NULL)
+		imap_sieve_run_deinit(&isrun_src);
 	mailbox_free(&sbox);
 	return ret;
 }
@@ -855,17 +970,25 @@ imap_sieve_mailbox_rules_init(struct mail_user *user)
 		setval = mail_user_plugin_getenv(user, str_c(identifier));
 		mbrule->after = p_strdup_empty(user->pool, setval);
 
+		str_truncate(identifier, id_len);
+		str_append(identifier, "_copy_source_after");
+		setval = mail_user_plugin_getenv(user, str_c(identifier));
+		mbrule->copy_source_after = p_strdup_empty(user->pool, setval);
+
 		if (user->mail_debug) {
 			imap_sieve_debug(user, "Static mailbox rule [%u]: "
 				"mailbox=`%s' from=`%s' causes=(%s) => "
-				"before=%s after=%s",
+				"before=%s after=%s%s",
 				mbrule->index, mbrule->mailbox,
 				(mbrule->from == NULL ? "*" : mbrule->from),
 				t_strarray_join(mbrule->causes, " "),
 				(mbrule->before == NULL ? "(none)" :
 					t_strconcat("`", mbrule->before, "'", NULL)),
 				(mbrule->after == NULL ? "(none)" :
-					t_strconcat("`", mbrule->after, "'", NULL)));
+					t_strconcat("`", mbrule->after, "'", NULL)),
+				(mbrule->copy_source_after == NULL ? "":
+					t_strconcat(" copy_source_after=`",
+						mbrule->copy_source_after, "'", NULL)));
 		}
 
 		if ((strcmp(mbrule->mailbox, "*") == 0 ||