diff --git a/components/mail/__tests__/compose-plugin-hooks.test.tsx b/components/mail/__tests__/compose-plugin-hooks.test.tsx
index 8e0c18506..8645e3d3e 100644
--- a/components/mail/__tests__/compose-plugin-hooks.test.tsx
+++ b/components/mail/__tests__/compose-plugin-hooks.test.tsx
@@ -3,6 +3,7 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { Mailbox } from '@/lib/jmap/types';
import { useAuthStore } from '@/stores/auth-store';
+import { useAccountStore } from '@/stores/account-store';
import { useEmailStore } from '@/stores/email-store';
import { useSettingsStore } from '@/stores/settings-store';
import { usePluginStore } from '@/stores/plugin-store';
@@ -222,6 +223,29 @@ describe('a mail link from another program in the desktop app', () => {
expect(screen.queryByText('stub composer')).not.toBeInTheDocument();
});
+ it('with two accounts it opens from the default one, in its own window, with no question', async () => {
+ const client = useAuthStore.getState().client;
+ useAuthStore.setState({ getAllConnectedClients: () => new Map([['login', client], ['second', client]]) as never });
+ useAccountStore.setState({
+ accounts: [
+ { id: 'login', email: 'me@example.com', isConnected: true, isDefault: false },
+ { id: 'second', email: 'work@example.com', isConnected: true, isDefault: true },
+ ],
+ defaultAccountId: 'second',
+ } as never);
+ try {
+ render();
+ await fromOs();
+ await waitFor(() => expect(desktopWindows.handOver).toHaveBeenCalledTimes(1));
+ expect(desktopWindows.open).toHaveBeenCalledWith('second');
+ expect(screen.queryByText('select_account_title')).not.toBeInTheDocument();
+ // The main window keeps the account it shows.
+ expect(useAuthStore.getState().activeAccountId).toBe('login');
+ } finally {
+ useAccountStore.setState({ accounts: [], defaultAccountId: null } as never);
+ }
+ });
+
it('a link inside the app still opens here', async () => {
render();
act(() => { requestInternalMailto('mailto:bob@example.com?subject=Hi'); });
diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx
index 8fd320a02..ca0f59b53 100644
--- a/components/mail/mail-app.tsx
+++ b/components/mail/mail-app.tsx
@@ -156,7 +156,7 @@ import { mailTitleContext } from "@/lib/tab-title";
import { usePluginStore } from "@/stores/plugin-store";
import { AppTopBannerSlot } from "@/components/plugins/app-top-banner-slot";
import { canDownload } from "@/lib/device-policy";
-import { consumePendingMailto, subscribeToPendingMailto } from "@/lib/protocol-handlers/session";
+import { consumePendingMailto, mailLinkAccountId, subscribeToPendingMailto } from "@/lib/protocol-handlers/session";
import { setNotificationReplyOpener } from "@/lib/pwa-notify/page-actions";
import { setMailLinkOpener } from "@/lib/desktop/mail-links";
import { requestTabReply } from "@/lib/pwa-notify/tab-reply";
@@ -1716,7 +1716,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
// A mail link from another program, in the desktop app: a compose window of its own,
// as Outlook and Thunderbird do, and the main window stays where it was. Plugins get
// their say first. False when it has to open here instead.
- const openMailtoInWindow = useCallback(async (pending: DraftRequest): Promise => {
+ const openMailtoInWindow = useCallback(async (pending: DraftRequest, accountId?: string): Promise => {
if (!pending.fromOs || !hasDesktopWindows() || !popoutsAvailable()) return false;
let prefill: ComposePrefill | null = null;
if (emailHooks.onBeforeCompose.size > 0) {
@@ -1729,7 +1729,8 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
// A plugin said no: nothing opens, here or there.
if (!prefill) return true;
}
- const opened = openComposerWindow(useAuthStore.getState().activeAccountId);
+ // The account the link goes out from, in its own window: the main window keeps the one it shows.
+ const opened = openComposerWindow(accountId ?? useAuthStore.getState().activeAccountId);
if (!opened) return false;
void opened.handOver({
mode: 'compose',
@@ -1768,6 +1769,10 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
}, [openMailtoInWindow, openMailtoHere]);
const openMailtoForAccount = useCallback(async (pending: DraftRequest, accountId: string) => {
+ if (await openMailtoInWindow(pending, accountId)) {
+ setPendingMailtoAccountChoice(null);
+ return;
+ }
setIsProtocolAccountSwitching(true);
try {
if (useAuthStore.getState().activeAccountId !== accountId) {
@@ -1778,7 +1783,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
} finally {
setIsProtocolAccountSwitching(false);
}
- }, [openMailtoDraft, switchAccount]);
+ }, [openMailtoDraft, openMailtoInWindow, switchAccount]);
// onMailtoIntercept: a plugin may stop a mailto: from opening the composer.
const mailtoAllowed = useCallback(async (pending: ParsedMailto & { href?: string }): Promise => {
@@ -1791,17 +1796,10 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
});
}, []);
- // A draft that comes from outside: which account it goes out from.
+ // A draft that comes from outside goes out from the default account, as in Outlook and
+ // Thunderbird, with no question: the composer's From changes it (desktop-p2-8).
const routeOutsideDraft = useCallback(async (pending: DraftRequest): Promise => {
- const protocolAccounts = getMailtoProtocolAccounts();
- if (protocolAccounts.length > 1) {
- // The question is asked in this window: bring it up.
- if (pending.fromOs) getDesktopBridge()?.windows?.focus?.();
- setPendingMailtoAccountChoice(pending);
- return;
- }
-
- const accountId = protocolAccounts[0]?.id ?? activeAccountId;
+ const accountId = mailLinkAccountId(getMailtoProtocolAccounts(), useAccountStore.getState().defaultAccountId, activeAccountId);
if (accountId) {
await openMailtoForAccount(pending, accountId);
return;
diff --git a/components/protocol/protocol-account-picker.tsx b/components/protocol/protocol-account-picker.tsx
index 9bc672a4a..bb594ce9e 100644
--- a/components/protocol/protocol-account-picker.tsx
+++ b/components/protocol/protocol-account-picker.tsx
@@ -167,7 +167,7 @@ export function ProtocolAccountPicker({
{t("switching_account")}
) : (
- {t("select_account_note")}
+
)}