From 4a9284aa48b3aaaa357312029ca91caf468ca39b Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:06:52 +0100
Subject: [PATCH] fix: mark the answered or forwarded message from a Pro
compose tab also when the send is held for undo
---
.../__tests__/pro-compose-tab-source.test.tsx | 19 +++++++++++++++++--
components/pro/pro-compose-tab-body.tsx | 12 +++++++-----
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/components/pro/__tests__/pro-compose-tab-source.test.tsx b/components/pro/__tests__/pro-compose-tab-source.test.tsx
index 24f08f8d3..c4cf191c9 100644
--- a/components/pro/__tests__/pro-compose-tab-source.test.tsx
+++ b/components/pro/__tests__/pro-compose-tab-source.test.tsx
@@ -6,7 +6,7 @@ import React from 'react';
// login and account they are in. Email ids repeat across accounts and
// servers, so the active login's client would act on its own namesake.
-const { activeClient, otherClient, markEmailKeyword, toastError } = vi.hoisted(() => {
+const { activeClient, otherClient, markEmailKeyword, toastError, sendResult } = vi.hoisted(() => {
const makeClient = (label: string) => ({
label,
getAccountId: () => `jmap-${label}`,
@@ -18,6 +18,7 @@ const { activeClient, otherClient, markEmailKeyword, toastError } = vi.hoisted((
otherClient: makeClient('account-2'),
markEmailKeyword: vi.fn(async () => {}),
toastError: vi.fn(),
+ sendResult: { scheduled: false },
};
});
@@ -51,7 +52,7 @@ vi.mock('@/stores/auth-store', () => {
});
vi.mock('@/stores/email-store', () => {
const state = {
- sendEmail: async () => ({ scheduled: false }),
+ sendEmail: async () => ({ ...sendResult }),
markEmailKeyword,
refreshCurrentMailbox: async () => {},
fetchScheduledEmails: async () => {},
@@ -77,6 +78,7 @@ import { ProComposeTabBody } from '../pro-compose-tab-body';
beforeEach(() => {
markEmailKeyword.mockClear();
+ sendResult.scheduled = false;
toastError.mockClear();
activeClient.deleteEmail.mockClear();
otherClient.deleteEmail.mockClear();
@@ -94,6 +96,19 @@ describe('Pro compose tab', () => {
expect(markEmailKeyword).toHaveBeenCalledWith(activeClient, 'eaaaaab', '$answered', source);
});
+ // A send delay (undo send) makes every send a held submission, so the
+ // flag has to be set before the held send returns, as in the inline composer (#985).
+ it.each([['reply', '$answered'], ['forward', '$forwarded']] as const)('flags the source of a held %s', async (mode, keyword) => {
+ sendResult.scheduled = true;
+ const source = { clientAccountId: 'local-2', accountId: 'jmap-group' };
+ render();
+
+ fireEvent.click(screen.getByText('send'));
+
+ await waitFor(() => expect(markEmailKeyword).toHaveBeenCalled());
+ expect(markEmailKeyword).toHaveBeenCalledWith(activeClient, 'eaaaaab', keyword, source);
+ });
+
it('discards the draft through the login that saved it', async () => {
render();
diff --git a/components/pro/pro-compose-tab-body.tsx b/components/pro/pro-compose-tab-body.tsx
index a30460dce..a3f1c72ce 100644
--- a/components/pro/pro-compose-tab-body.tsx
+++ b/components/pro/pro-compose-tab-body.tsx
@@ -100,13 +100,10 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) {
}
announceSent(result, sendData);
- if (result.scheduled) {
- await handleScheduledSendCreated();
- return;
- }
-
// Mark the original message as $answered / $forwarded so the standard
// viewer and list reflect the action (same behaviour as inline compose).
+ // Before the scheduled return: with a send delay every send is a held
+ // submission, and the flag was never set. (#985)
if (data.sourceEmailId && (data.mode === 'reply' || data.mode === 'replyAll')) {
try {
await useEmailStore.getState().markEmailKeyword(client, data.sourceEmailId, '$answered', data.sourceEmailSource ?? undefined);
@@ -121,6 +118,11 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) {
}
}
+ if (result.scheduled) {
+ await handleScheduledSendCreated();
+ return;
+ }
+
// Refresh the currently-active mail list so the new sent message /
// updated keyword status shows up.
await refreshCurrentMailbox(client);