diff --git a/app/api/dev-jmap/[...path]/route.ts b/app/api/dev-jmap/[...path]/route.ts index 1b0bd535..d1702813 100644 --- a/app/api/dev-jmap/[...path]/route.ts +++ b/app/api/dev-jmap/[...path]/route.ts @@ -1553,12 +1553,16 @@ function handleThreadGet(args: MethodArgs, callId: string): MethodResult { function handleEmailSubmissionSet(args: MethodArgs, callId: string): MethodResult { const created: Record = {}; const updated: Record = {}; - const create = args.create as Record | undefined; + const create = args.create as Record | undefined; if (create) { for (const [key, value] of Object.entries(create)) { const id = `submission-${Date.now()}-${key}`; + const holdFor = value.envelope?.mailFrom?.parameters?.HOLDFOR; const holdUntil = value.envelope?.mailFrom?.parameters?.HOLDUNTIL; - const holdUntilTime = holdUntil ? new Date(holdUntil).getTime() : Number.NaN; + const holdForSeconds = holdFor ? Number(holdFor) : Number.NaN; + const holdUntilTime = Number.isFinite(holdForSeconds) && holdForSeconds > 0 + ? Date.now() + holdForSeconds * 1000 + : holdUntil ? new Date(holdUntil).getTime() : Number.NaN; const delayedUntil = Number.isFinite(holdUntilTime) ? new Date(holdUntilTime).toISOString() : undefined; created[key] = { id, ...(delayedUntil ? { sendAt: delayedUntil } : {}) }; if (delayedUntil && value.emailId && value.identityId) { @@ -1583,8 +1587,8 @@ function handleEmailSubmissionSet(args: MethodArgs, callId: string): MethodResul function handleEmailSubmissionQuery(args: MethodArgs, callId: string): MethodResult { const position = Number(args.position || 0); const limit = Number(args.limit || 50); - const pending = scheduledSubmissions.filter(s => s.undoStatus === 'pending').sort((a, b) => new Date(a.sendAt).getTime() - new Date(b.sendAt).getTime()); - return ['EmailSubmission/query', { accountId: ACCOUNT_ID, queryState: nextState(), ids: pending.slice(position, position + limit).map(s => s.id), total: pending.length, position, canCalculateChanges: false }, callId]; + const submissions = [...scheduledSubmissions].sort((a, b) => new Date(a.sendAt).getTime() - new Date(b.sendAt).getTime()); + return ['EmailSubmission/query', { accountId: ACCOUNT_ID, queryState: nextState(), ids: submissions.slice(position, position + limit).map(s => s.id), total: submissions.length, position, canCalculateChanges: false }, callId]; } function handleEmailSubmissionGet(args: MethodArgs, callId: string): MethodResult { diff --git a/components/email/email-context-menu.tsx b/components/email/email-context-menu.tsx index a8a8d790..fbcce94d 100644 --- a/components/email/email-context-menu.tsx +++ b/components/email/email-context-menu.tsx @@ -152,6 +152,7 @@ export function EmailContextMenu({ const showBatchActions = isMultiSelect && selectedCount > 1; const isInJunkFolder = currentMailboxRole === 'junk'; const isScheduled = email.isScheduled === true; + const canCancelScheduled = isScheduled && email.scheduledUndoStatus === 'pending'; // Build color options from keyword definitions in settings const colorOptions = emailKeywords.map((kw) => ({ @@ -205,7 +206,7 @@ export function EmailContextMenu({ )} - {isScheduled && !showBatchActions && ( + {isScheduled && !showBatchActions && canCancelScheduled && ( <> )} - {isScheduled && } + {canCancelScheduled && } {!isScheduled && ( <> diff --git a/components/email/email-list.tsx b/components/email/email-list.tsx index f9f4679f..4d9149ef 100644 --- a/components/email/email-list.tsx +++ b/components/email/email-list.tsx @@ -505,30 +505,39 @@ export function EmailList({ /> {isScheduledView && thread.latestEmail.isScheduled && (
+ {thread.latestEmail.scheduledUndoStatus && thread.latestEmail.scheduledUndoStatus !== 'pending' && ( + + {thread.latestEmail.scheduledUndoStatus} + + )} {new Date(thread.latestEmail.scheduledSendAt || '').toLocaleString()} - - - + {thread.latestEmail.scheduledUndoStatus === 'pending' && ( + <> + + + + + )}
)} diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index f4d4de79..ff263720 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -871,6 +871,7 @@ export function EmailViewer({ // Detect if the email is a draft const isDraft = email?.keywords?.['$draft'] === true; const isScheduled = email?.isScheduled === true; + const canCancelScheduled = isScheduled && email?.scheduledUndoStatus === 'pending'; // Color options for email tags (from user-defined keyword settings) const colorOptions = emailKeywords.map((kw) => ({ @@ -3137,7 +3138,7 @@ export function EmailViewer({ )} - {isScheduled && ( + {isScheduled && canCancelScheduled && ( <> - - + {canCancelScheduled && ( + <> + + + + + )} diff --git a/lib/__tests__/jmap-send-threading.test.ts b/lib/__tests__/jmap-send-threading.test.ts index 8524af8b..6c6b06ec 100644 --- a/lib/__tests__/jmap-send-threading.test.ts +++ b/lib/__tests__/jmap-send-threading.test.ts @@ -16,14 +16,22 @@ function enableDelayedSend(client: JMAPClient) { capabilities: { 'urn:ietf:params:jmap:core': {}, 'urn:ietf:params:jmap:mail': {}, - 'urn:ietf:params:jmap:submission': { maxDelayedSend: 3600, submissionExtensions: ['FUTURERELEASE'] }, + 'urn:ietf:params:jmap:submission': {}, }, session: { + primaryAccounts: { + 'urn:ietf:params:jmap:mail': 'account-1', + 'urn:ietf:params:jmap:submission': 'submission-account-1', + }, accounts: { 'account-1': { accountCapabilities: { 'urn:ietf:params:jmap:mail': {}, - 'urn:ietf:params:jmap:submission': { maxDelayedSend: 3600, submissionExtensions: ['FUTURERELEASE'] }, + }, + }, + 'submission-account-1': { + accountCapabilities: { + 'urn:ietf:params:jmap:submission': { maxDelayedSend: 3600, submissionExtensions: { FUTURERELEASE: true } }, }, }, }, @@ -189,6 +197,7 @@ describe('JMAPClient.sendEmail threading headers', () => { const identityRequest = captured[1]; expect(identityRequest.using).toContain('urn:ietf:params:jmap:submission'); const submissionCall = captured[2].methodCalls.find(call => call[0] === 'EmailSubmission/set'); + expect(submissionCall?.[1].accountId).toBe('submission-account-1'); expect(submissionCall?.[1].create).toEqual({ '1': { emailId: expect.stringMatching(/^#send-/), @@ -196,7 +205,7 @@ describe('JMAPClient.sendEmail threading headers', () => { envelope: { mailFrom: { email: 'user@example.com', - parameters: { HOLDUNTIL: expect.stringMatching(/^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} \+0000$/) }, + parameters: { HOLDFOR: expect.stringMatching(/^\d+$/) }, }, }, }, diff --git a/lib/demo/demo-client.ts b/lib/demo/demo-client.ts index bbda377e..f2199e44 100644 --- a/lib/demo/demo-client.ts +++ b/lib/demo/demo-client.ts @@ -58,7 +58,7 @@ export class DemoJMAPClient implements IJMAPClient { return { 'urn:ietf:params:jmap:core': { maxSizeUpload: 50_000_000, maxCallsInRequest: 16, maxObjectsInGet: 500 }, 'urn:ietf:params:jmap:mail': {}, - 'urn:ietf:params:jmap:submission': { maxDelayedSend: 30 * 24 * 60 * 60, submissionExtensions: ['FUTURERELEASE'] }, + 'urn:ietf:params:jmap:submission': { maxDelayedSend: 30 * 24 * 60 * 60, submissionExtensions: { FUTURERELEASE: true } }, 'urn:ietf:params:jmap:vacationresponse': {}, 'urn:ietf:params:jmap:contacts': {}, 'urn:ietf:params:jmap:calendars': {}, diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index 4a2ae37d..0ab0bb8d 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -337,27 +337,23 @@ function sanitizeIdentityDisplayName(name: string | undefined | null): string { return name.replace(/\s*<[^>]*>\s*$/, '').trim(); } -function formatHoldUntil(delayedUntil: string): string { - const date = new Date(delayedUntil); - const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - const pad = (value: number) => String(value).padStart(2, '0'); - - return `${weekdays[date.getUTCDay()]}, ${pad(date.getUTCDate())} ${months[date.getUTCMonth()]} ${date.getUTCFullYear()} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} +0000`; -} - -function createDelayedSubmissionEnvelope(fromEmail: string, delayedUntil?: string): Record | undefined { - if (!delayedUntil) return undefined; +function createDelayedSubmissionEnvelope(fromEmail: string, holdForSeconds?: number): Record | undefined { + if (!holdForSeconds) return undefined; return { mailFrom: { email: fromEmail, parameters: { - HOLDUNTIL: formatHoldUntil(delayedUntil), + HOLDFOR: String(holdForSeconds), }, }, }; } +type SubmissionCapability = { + maxDelayedSend?: number; + submissionExtensions?: unknown; +}; + export class JMAPClient implements IJMAPClient { private static readonly RATE_LIMIT_TOAST_THROTTLE_MS = 10_000; @@ -2105,7 +2101,7 @@ export class JMAPClient implements IJMAPClient { references?: string[], delayedUntil?: string ): Promise { - if (delayedUntil) this.validateDelayedUntil(delayedUntil); + const holdForSeconds = delayedUntil ? this.validateDelayedUntil(delayedUntil) : undefined; const emailId = `send-${Date.now()}`; const mailboxes = await this.getMailboxes(); const sentMailbox = mailboxes.find(mb => mb.role === 'sent'); @@ -2213,10 +2209,10 @@ export class JMAPClient implements IJMAPClient { const submissionCreate = { emailId: `#${emailId}`, identityId: finalIdentityId, - ...(delayedUntil ? { envelope: createDelayedSubmissionEnvelope(fromEmail || this.username, delayedUntil) } : {}), + ...(holdForSeconds ? { envelope: createDelayedSubmissionEnvelope(fromEmail || this.username, holdForSeconds) } : {}), }; methodCalls.push(["EmailSubmission/set", { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { "1": submissionCreate }, onSuccessUpdateEmail, }, "2"]); @@ -2228,10 +2224,10 @@ export class JMAPClient implements IJMAPClient { const submissionCreate = { emailId: `#${emailId}`, identityId: finalIdentityId, - ...(delayedUntil ? { envelope: createDelayedSubmissionEnvelope(fromEmail || this.username, delayedUntil) } : {}), + ...(holdForSeconds ? { envelope: createDelayedSubmissionEnvelope(fromEmail || this.username, holdForSeconds) } : {}), }; methodCalls.push(["EmailSubmission/set", { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { "1": submissionCreate }, onSuccessUpdateEmail, }, "1"]); @@ -2435,7 +2431,7 @@ export class JMAPClient implements IJMAPClient { create: { [emailId]: emailCreate }, }, "0"], ["EmailSubmission/set", { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { "sub-1": { emailId: `#${emailId}`, identityId: finalIdentityId } }, onSuccessUpdateEmail: { "#sub-1": { @@ -2613,7 +2609,7 @@ export class JMAPClient implements IJMAPClient { create: { [emailId]: emailCreate }, }, "0"], ["EmailSubmission/set", { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { "sub-1": { emailId: `#${emailId}`, identityId } }, onSuccessUpdateEmail: { "#sub-1": { @@ -2765,7 +2761,7 @@ export class JMAPClient implements IJMAPClient { create: { [emailId]: emailCreate }, }, "0"], ["EmailSubmission/set", { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { "sub-1": { emailId: `#${emailId}`, identityId } }, onSuccessUpdateEmail: { "#sub-1": { @@ -2902,27 +2898,21 @@ export class JMAPClient implements IJMAPClient { } getMaxDelayedSend(accountId?: string): number { - const id = accountId || this.accountId; - const accountCapability = this.session?.accounts?.[id]?.accountCapabilities?.["urn:ietf:params:jmap:submission"] as { maxDelayedSend?: number } | undefined; - const sessionCapability = this.session?.capabilities?.["urn:ietf:params:jmap:submission"] as { maxDelayedSend?: number } | undefined; - const maxDelayedSend = accountCapability?.maxDelayedSend ?? sessionCapability?.maxDelayedSend; + const maxDelayedSend = this.getSubmissionCapability(accountId)?.maxDelayedSend; return typeof maxDelayedSend === 'number' ? maxDelayedSend : 0; } hasDelayedSend(accountId?: string): boolean { - const id = accountId || this.accountId; - const accountCapability = this.session?.accounts?.[id]?.accountCapabilities?.["urn:ietf:params:jmap:submission"] as { submissionExtensions?: unknown } | undefined; - const sessionCapability = this.session?.capabilities?.["urn:ietf:params:jmap:submission"] as { submissionExtensions?: unknown } | undefined; - const submissionExtensions = accountCapability?.submissionExtensions ?? sessionCapability?.submissionExtensions; - const hasFutureRelease = Array.isArray(submissionExtensions) - && submissionExtensions.some(extension => typeof extension === 'string' && extension.toUpperCase() === 'FUTURERELEASE'); + const submissionCapability = this.getSubmissionCapability(accountId); + const submissionExtensions = submissionCapability?.submissionExtensions; + const hasFutureRelease = this.hasSubmissionExtension(submissionExtensions, 'FUTURERELEASE'); - return this.supportsEmailSubmission() + return !!submissionCapability && hasFutureRelease - && this.getMaxDelayedSend(id) > 0; + && this.getMaxDelayedSend(accountId) > 0; } - private validateDelayedUntil(delayedUntil: string, accountId?: string): void { + private validateDelayedUntil(delayedUntil: string, accountId?: string): number { const time = new Date(delayedUntil).getTime(); if (!Number.isFinite(time)) { throw new Error('Scheduled send time is invalid'); @@ -2938,12 +2928,13 @@ export class JMAPClient implements IJMAPClient { if (time > now + maxDelayedSend * 1000) { throw new Error('Scheduled send time is later than the server allows'); } + return Math.ceil((time - now) / 1000); } private async getEmailSubmissionSendAt(submissionId: string): Promise { const response = await this.request([ ['EmailSubmission/get', { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), ids: [submissionId], properties: ['sendAt', 'undoStatus'], }, '0'], @@ -2952,6 +2943,27 @@ export class JMAPClient implements IJMAPClient { return submission?.sendAt; } + private getSubmissionAccountId(accountId?: string): string { + return accountId || this.session?.primaryAccounts?.['urn:ietf:params:jmap:submission'] || this.accountId; + } + + private getSubmissionCapability(accountId?: string): SubmissionCapability | undefined { + const submissionAccountId = this.getSubmissionAccountId(accountId); + return this.session?.accounts?.[submissionAccountId]?.accountCapabilities?.['urn:ietf:params:jmap:submission'] as SubmissionCapability | undefined; + } + + private hasSubmissionExtension(submissionExtensions: unknown, extension: string): boolean { + const target = extension.toUpperCase(); + if (Array.isArray(submissionExtensions)) { + return submissionExtensions.some(item => typeof item === 'string' && item.toUpperCase() === target); + } + if (submissionExtensions && typeof submissionExtensions === 'object') { + return Object.entries(submissionExtensions as Record) + .some(([key, value]) => key.toUpperCase() === target && value !== false && value != null); + } + return false; + } + getEventSourceUrl(): string | null { if (!this.session) return null; @@ -5397,7 +5409,7 @@ export class JMAPClient implements IJMAPClient { async submitEmail(emailId: string, identityId: string): Promise { const response = await this.request([ ['EmailSubmission/set', { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { 'smime-submit': { emailId, identityId } }, }, '0'], ]); @@ -5420,7 +5432,7 @@ export class JMAPClient implements IJMAPClient { draftMailboxId?: string, delayedUntil?: string, ): Promise { - if (delayedUntil) this.validateDelayedUntil(delayedUntil); + const holdForSeconds = delayedUntil ? this.validateDelayedUntil(delayedUntil) : undefined; // Upload the raw message const file = new File([blob], 'message.eml', { type: 'message/rfc822' }); const { blobId } = await this.uploadBlob(file); @@ -5430,7 +5442,7 @@ export class JMAPClient implements IJMAPClient { const importMailboxId = draftMailboxId || sentMailboxId; const identities = await this.getIdentities(); const identity = identities.find(item => item.id === identityId); - const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, delayedUntil); + const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, holdForSeconds); const methodCalls: [string, Record, string][] = [ ['Email/import', { @@ -5444,7 +5456,7 @@ export class JMAPClient implements IJMAPClient { }, }, '0'], ['EmailSubmission/set', { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { 'raw-submit': { emailId: '#raw-import', @@ -5505,9 +5517,7 @@ export class JMAPClient implements IJMAPClient { const queryResponse = await this.request([ ['EmailSubmission/query', { - accountId: this.accountId, - filter: { undoStatus: 'pending' }, - sort: [{ property: 'sendAt', isAscending: true }], + accountId: this.getSubmissionAccountId(), limit, position, }, '0'], @@ -5521,13 +5531,18 @@ export class JMAPClient implements IJMAPClient { const submissionResponse = await this.request([ ['EmailSubmission/get', { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), ids, - properties: ['id', 'emailId', 'identityId', 'sendAt', 'undoStatus'], + properties: ['id', 'emailId', 'identityId', 'threadId', 'sendAt', 'undoStatus', 'deliveryStatus'], }, '0'], ]); + const now = Date.now(); const submissions = ((submissionResponse.methodResponses?.[0]?.[1]?.list ?? []) as EmailSubmission[]) - .filter(submission => submission.sendAt && Number.isFinite(new Date(submission.sendAt).getTime())); + .filter(submission => { + if (!submission.sendAt) return false; + const sendAtTime = new Date(submission.sendAt).getTime(); + return Number.isFinite(sendAtTime) && sendAtTime > now; + }); if (submissions.length === 0) { return { emails: [], hasMore: false, total: query?.total ?? 0 }; @@ -5556,10 +5571,12 @@ export class JMAPClient implements IJMAPClient { if (!email || !submission.sendAt) return null; return { ...email, + threadId: submission.threadId || email.threadId, scheduledSendAt: submission.sendAt, emailSubmissionId: submission.id, scheduledIdentityId: submission.identityId, scheduledUndoStatus: submission.undoStatus, + scheduledDeliveryStatus: submission.deliveryStatus, isScheduled: true, isSmimeScheduled: isSmimeEmail(email), }; @@ -5574,7 +5591,7 @@ export class JMAPClient implements IJMAPClient { async cancelEmailSubmission(submissionId: string): Promise { const response = await this.request([ ['EmailSubmission/set', { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), update: { [submissionId]: { undoStatus: 'canceled' } }, }, '0'], ]); @@ -5586,16 +5603,16 @@ export class JMAPClient implements IJMAPClient { } async rescheduleEmailSubmission(submissionId: string, emailId: string, identityId: string, delayedUntil: string): Promise { - this.validateDelayedUntil(delayedUntil); + const holdForSeconds = this.validateDelayedUntil(delayedUntil); const mailboxes = await this.getMailboxes(); const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts'); const sentMailbox = mailboxes.find(mb => mb.role === 'sent'); const identities = await this.getIdentities(); const identity = identities.find(item => item.id === identityId); - const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, delayedUntil); + const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, holdForSeconds); const response = await this.request([ ['EmailSubmission/set', { - accountId: this.accountId, + accountId: this.getSubmissionAccountId(), create: { replacement: { emailId, identityId, ...(envelope ? { envelope } : {}) } }, ...(draftsMailbox && sentMailbox ? { onSuccessUpdateEmail: { diff --git a/lib/jmap/types.ts b/lib/jmap/types.ts index 27eab3d0..d43eea46 100644 --- a/lib/jmap/types.ts +++ b/lib/jmap/types.ts @@ -47,6 +47,7 @@ export interface Email { emailSubmissionId?: string; scheduledIdentityId?: string; scheduledUndoStatus?: 'pending' | 'final' | 'canceled'; + scheduledDeliveryStatus?: Record; isScheduled?: boolean; isSmimeScheduled?: boolean; } @@ -64,6 +65,7 @@ export interface ScheduledEmail extends Email { emailSubmissionId: string; scheduledIdentityId: string; scheduledUndoStatus: 'pending' | 'final' | 'canceled'; + scheduledDeliveryStatus?: Record; isScheduled: true; isSmimeScheduled: boolean; } diff --git a/stores/email-store.ts b/stores/email-store.ts index e85192c9..fdbe58bf 100644 --- a/stores/email-store.ts +++ b/stores/email-store.ts @@ -18,6 +18,8 @@ type ScheduledSubmissionMetadata = { undoStatus: 'pending' | 'final' | 'canceled'; }; +const VIRTUAL_SCHEDULED_MAILBOX_ID = '__scheduled__'; + type PendingUndoSend = { submissionId: string; emailId?: string; sendAt: string; isSmime: boolean }; interface EmailStore { @@ -400,7 +402,8 @@ export const useEmailStore = create((set, get) => ({ // Auto-select inbox if no mailbox is selected or the current selection // doesn't exist in the fetched list (e.g. after an account switch) const currentSelectedMailbox = get().selectedMailbox; - const selectionValid = currentSelectedMailbox && mailboxes.some(m => m.id === currentSelectedMailbox); + const selectionValid = currentSelectedMailbox === VIRTUAL_SCHEDULED_MAILBOX_ID + || (currentSelectedMailbox && mailboxes.some(m => m.id === currentSelectedMailbox)); const loadingPatch = isInitialLoad ? { isLoading: false } : {}; if (!selectionValid) { // Find inbox from PRIMARY account (not shared accounts) @@ -425,6 +428,11 @@ export const useEmailStore = create((set, get) => ({ set({ isLoading: true, error: null }); // Keep previous emails visible during transition try { const targetMailboxId = mailboxId || get().selectedMailbox; + if (targetMailboxId === VIRTUAL_SCHEDULED_MAILBOX_ID) { + set({ isLoading: false, emails: [], hasMoreEmails: false, totalEmails: 0 }); + await get().fetchScheduledEmails(client); + return; + } // Find the mailbox to get its accountId (for shared folder support) const mailboxes = get().mailboxes; @@ -511,6 +519,12 @@ export const useEmailStore = create((set, get) => ({ set({ isLoadingMore: true, error: null }); try { + if (selectedMailbox === VIRTUAL_SCHEDULED_MAILBOX_ID) { + set({ isLoadingMore: false }); + await get().loadMoreScheduledEmails(client); + return; + } + // Get emails per page from settings const emailsPerPage = useSettingsStore.getState().emailsPerPage; @@ -1678,6 +1692,11 @@ export const useEmailStore = create((set, get) => ({ // Only refresh if a mailbox is currently selected if (!selectedMailbox) return; + if (selectedMailbox === VIRTUAL_SCHEDULED_MAILBOX_ID) { + await get().fetchScheduledEmails(client); + return; + } + try { // Fetch emails for the current mailbox without clearing the list first // This provides a smoother update experience @@ -2040,7 +2059,10 @@ export const useEmailStore = create((set, get) => ({ }); }, - setScheduledView: (isScheduledView) => set({ isScheduledView }), + setScheduledView: (isScheduledView) => set(state => ({ + isScheduledView, + selectedMailbox: isScheduledView ? VIRTUAL_SCHEDULED_MAILBOX_ID : state.selectedMailbox, + })), clearPendingUndoSend: () => set({ pendingUndoSend: null }), fetchScheduledEmails: async (client) => {