diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index 8175b91f..b8b25216 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -473,12 +473,14 @@ export default function Home() {
const handleDelete = async () => {
if (!client || !selectedEmail) return;
- // Check if we're currently in the trash folder
+ // Check if we're currently in the trash or junk folder
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
const isInTrash = currentMailbox?.role === 'trash';
+ const isInJunk = currentMailbox?.role === 'junk';
+ const permanentlyDeleteJunk = useSettingsStore.getState().permanentlyDeleteJunk;
- if (isInTrash) {
- // In trash: confirm before permanently deleting
+ if (isInTrash || (isInJunk && permanentlyDeleteJunk)) {
+ // In trash or junk with permanent delete enabled: confirm before permanently deleting
const confirmed = await confirmDialog({
title: t('email_list.permanent_delete_confirm_title'),
message: t('email_list.permanent_delete_confirm_message'),
diff --git a/components/email/email-list.tsx b/components/email/email-list.tsx
index b1b2ebab..5c02d802 100644
--- a/components/email/email-list.tsx
+++ b/components/email/email-list.tsx
@@ -4,7 +4,7 @@ import { Email, ThreadGroup } from "@/lib/jmap/types";
import { ThreadListItem } from "./thread-list-item";
import { EmailContextMenu } from "./email-context-menu";
import { cn } from "@/lib/utils";
-import { Trash2, Mail, MailX, MailOpen, Loader2, SearchX } from "lucide-react";
+import { Trash2, Mail, MailX, MailOpen, Loader2, SearchX, AlertTriangle } from "lucide-react";
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
@@ -74,6 +74,7 @@ export function EmailList({
isLoadingMore,
mailboxes,
selectedMailbox,
+ emptyMailbox,
expandedThreadIds,
threadEmailsCache,
isLoadingThread,
@@ -172,6 +173,28 @@ export function EmailList({
}
};
+ const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
+ const isEmptyableFolder = currentMailbox?.role === 'trash' || currentMailbox?.role === 'junk';
+
+ const handleEmptyFolder = async () => {
+ if (!client || isProcessing || !currentMailbox) return;
+
+ const confirmed = await confirmDialog({
+ title: t('empty_folder.confirm_title'),
+ message: t('empty_folder.confirm_message'),
+ confirmText: t('empty_folder.confirm_button'),
+ variant: "destructive",
+ });
+ if (!confirmed) return;
+
+ setIsProcessing(true);
+ try {
+ await emptyMailbox(client, currentMailbox.id);
+ } finally {
+ setTimeout(() => setIsProcessing(false), 500);
+ }
+ };
+
const handleLoadMore = useCallback(() => {
if (client && hasMoreEmails && !isLoadingMore && !isLoading) {
loadMoreEmails(client);
@@ -308,7 +331,29 @@ export function EmailList({
/>
)}
-
+ {/* Empty Folder Banner for Junk/Trash */}
+ {isEmptyableFolder && emails.length > 0 && !hasSelection && (
+
+
+
+
{currentMailbox?.role === 'junk' ? t('empty_folder.junk_hint') : t('empty_folder.trash_hint')}
+
+
+
+ )}
{/* Email List */}
diff --git a/components/settings/email-settings.tsx b/components/settings/email-settings.tsx
index ee13b5e9..e3dfe3d1 100644
--- a/components/settings/email-settings.tsx
+++ b/components/settings/email-settings.tsx
@@ -14,6 +14,7 @@ export function EmailSettings() {
const {
markAsReadDelay,
deleteAction,
+ permanentlyDeleteJunk,
showPreview,
emailsPerPage,
externalContentPolicy,
@@ -65,6 +66,14 @@ export function EmailSettings() {
+ {/* Permanently Delete Junk */}
+
+ updateSetting('permanentlyDeleteJunk', checked)}
+ />
+
+
{/* Show Preview */}
updateSetting('showPreview', checked)} />
diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts
index cd63f3d5..10f8b9c5 100644
--- a/lib/jmap/client.ts
+++ b/lib/jmap/client.ts
@@ -808,6 +808,34 @@ export class JMAPClient {
}
}
+ async emptyMailbox(mailboxId: string): Promise {
+ let totalDestroyed = 0;
+ let hasMore = true;
+
+ while (hasMore) {
+ const response = await this.request([
+ ["Email/query", {
+ accountId: this.accountId,
+ filter: { inMailbox: mailboxId },
+ limit: 500,
+ }, "0"],
+ ["Email/set", {
+ accountId: this.accountId,
+ "#destroy": { resultOf: "0", name: "Email/query", path: "/ids" },
+ }, "1"],
+ ]);
+
+ const queryResult = response.methodResponses?.[0]?.[1];
+ const setResult = response.methodResponses?.[1]?.[1];
+ const destroyed = setResult?.destroyed?.length || 0;
+ totalDestroyed += destroyed;
+
+ hasMore = destroyed > 0 && (queryResult?.total || 0) > destroyed;
+ }
+
+ return totalDestroyed;
+ }
+
async markAsSpam(emailId: string, accountId?: string): Promise {
const targetAccountId = accountId || this.accountId;
diff --git a/locales/de/common.json b/locales/de/common.json
index 59a38fdc..5ded4634 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Endgültig löschen",
"permanent_delete_confirm_title": "Endgültig löschen",
"permanent_delete_confirm_message": "Diese E-Mail wird endgültig gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
- "permanent_delete_confirm_batch_message": "Diese {count, plural, one {1 E-Mail} other {# E-Mails}} werden endgültig gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
+ "permanent_delete_confirm_batch_message": "Diese {count, plural, one {1 E-Mail} other {# E-Mails}} werden endgültig gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
+ "empty_folder": {
+ "button": "Ordner leeren",
+ "confirm_title": "Ordner leeren",
+ "confirm_message": "Alle E-Mails in diesem Ordner werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
+ "confirm_button": "Ordner leeren",
+ "junk_hint": "Sie können den Spam-Ordner leeren, um alle Nachrichten dauerhaft zu entfernen.",
+ "trash_hint": "Sie können den Papierkorb leeren, um alle Nachrichten dauerhaft zu entfernen."
+ }
},
"email_viewer": {
"no_email_selected": "Keine E-Mail ausgewählt",
@@ -594,6 +602,10 @@
"permanent": "Dauerhaft löschen",
"warning": "E-Mails werden dauerhaft gelöscht und können nicht wiederhergestellt werden. Diese Aktion ist unwiderruflich."
},
+ "permanently_delete_junk": {
+ "label": "Spam dauerhaft löschen",
+ "description": "E-Mails aus dem Spam-Ordner dauerhaft löschen, anstatt sie in den Papierkorb zu verschieben"
+ },
"show_preview": {
"label": "Vorschautext anzeigen",
"description": "E-Mail-Vorschau in der Liste anzeigen"
diff --git a/locales/en/common.json b/locales/en/common.json
index 8f8d4f28..50852f7e 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Delete permanently",
"permanent_delete_confirm_title": "Permanently delete",
"permanent_delete_confirm_message": "This email will be permanently deleted. This action cannot be undone.",
- "permanent_delete_confirm_batch_message": "These {count, plural, one {1 email} other {# emails}} will be permanently deleted. This action cannot be undone."
+ "permanent_delete_confirm_batch_message": "These {count, plural, one {1 email} other {# emails}} will be permanently deleted. This action cannot be undone.",
+ "empty_folder": {
+ "button": "Empty folder",
+ "confirm_title": "Empty folder",
+ "confirm_message": "All emails in this folder will be permanently deleted. This action cannot be undone.",
+ "confirm_button": "Empty folder",
+ "junk_hint": "You can empty the Junk folder to permanently remove all messages.",
+ "trash_hint": "You can empty the Trash folder to permanently remove all messages."
+ }
},
"email_viewer": {
"no_email_selected": "No email selected",
@@ -598,6 +606,10 @@
"permanent": "Delete Permanently",
"warning": "Emails will be permanently deleted and cannot be recovered. This action is irreversible."
},
+ "permanently_delete_junk": {
+ "label": "Permanently Delete Junk",
+ "description": "Permanently delete emails from the Junk/Spam folder instead of moving them to Trash"
+ },
"show_preview": {
"label": "Show Preview Text",
"description": "Display email preview in the list"
diff --git a/locales/es/common.json b/locales/es/common.json
index ec28e783..eebde46f 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Eliminar permanentemente",
"permanent_delete_confirm_title": "Eliminar permanentemente",
"permanent_delete_confirm_message": "Este correo se eliminará permanentemente. Esta acción no se puede deshacer.",
- "permanent_delete_confirm_batch_message": "Estos {count, plural, one {1 correo} other {# correos}} se eliminarán permanentemente. Esta acción no se puede deshacer."
+ "permanent_delete_confirm_batch_message": "Estos {count, plural, one {1 correo} other {# correos}} se eliminarán permanentemente. Esta acción no se puede deshacer.",
+ "empty_folder": {
+ "button": "Vaciar carpeta",
+ "confirm_title": "Vaciar carpeta",
+ "confirm_message": "Todos los correos de esta carpeta se eliminarán permanentemente. Esta acción no se puede deshacer.",
+ "confirm_button": "Vaciar carpeta",
+ "junk_hint": "Puede vaciar la carpeta de Spam para eliminar permanentemente todos los mensajes.",
+ "trash_hint": "Puede vaciar la Papelera para eliminar permanentemente todos los mensajes."
+ }
},
"email_viewer": {
"no_email_selected": "Ningún correo seleccionado",
@@ -594,6 +602,10 @@
"permanent": "Eliminar Permanentemente",
"warning": "Los correos se eliminarán permanentemente y no se podrán recuperar. Esta acción es irreversible."
},
+ "permanently_delete_junk": {
+ "label": "Eliminar spam permanentemente",
+ "description": "Eliminar permanentemente los correos de la carpeta Spam en lugar de moverlos a la Papelera"
+ },
"show_preview": {
"label": "Mostrar Vista Previa",
"description": "Mostrar vista previa del correo en la lista"
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 0c920d32..ff01e6ce 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Supprimer définitivement",
"permanent_delete_confirm_title": "Supprimer définitivement",
"permanent_delete_confirm_message": "Cet e-mail sera définitivement supprimé. Cette action est irréversible.",
- "permanent_delete_confirm_batch_message": "Ces {count, plural, one {1 e-mail} other {# e-mails}} seront définitivement supprimés. Cette action est irréversible."
+ "permanent_delete_confirm_batch_message": "Ces {count, plural, one {1 e-mail} other {# e-mails}} seront définitivement supprimés. Cette action est irréversible.",
+ "empty_folder": {
+ "button": "Vider le dossier",
+ "confirm_title": "Vider le dossier",
+ "confirm_message": "Tous les e-mails de ce dossier seront définitivement supprimés. Cette action est irréversible.",
+ "confirm_button": "Vider le dossier",
+ "junk_hint": "Vous pouvez vider le dossier Indésirables pour supprimer définitivement tous les messages.",
+ "trash_hint": "Vous pouvez vider la corbeille pour supprimer définitivement tous les messages."
+ }
},
"email_viewer": {
"no_email_selected": "Aucun email sélectionné",
@@ -594,6 +602,10 @@
"permanent": "Supprimer définitivement",
"warning": "Les emails seront supprimés définitivement et ne pourront pas être récupérés. Cette action est irréversible."
},
+ "permanently_delete_junk": {
+ "label": "Supprimer définitivement les indésirables",
+ "description": "Supprimer définitivement les e-mails du dossier Indésirables au lieu de les déplacer vers la corbeille"
+ },
"show_preview": {
"label": "Afficher l'aperçu",
"description": "Afficher l'aperçu de l'email dans la liste"
diff --git a/locales/it/common.json b/locales/it/common.json
index 21599c9d..5fab4799 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Elimina definitivamente",
"permanent_delete_confirm_title": "Elimina definitivamente",
"permanent_delete_confirm_message": "Questa e-mail verrà eliminata definitivamente. Questa azione non può essere annullata.",
- "permanent_delete_confirm_batch_message": "Queste {count, plural, one {1 e-mail} other {# e-mail}} verranno eliminate definitivamente. Questa azione non può essere annullata."
+ "permanent_delete_confirm_batch_message": "Queste {count, plural, one {1 e-mail} other {# e-mail}} verranno eliminate definitivamente. Questa azione non può essere annullata.",
+ "empty_folder": {
+ "button": "Svuota cartella",
+ "confirm_title": "Svuota cartella",
+ "confirm_message": "Tutti i messaggi in questa cartella verranno eliminati definitivamente. Questa azione non può essere annullata.",
+ "confirm_button": "Svuota cartella",
+ "junk_hint": "Puoi svuotare la cartella Spam per rimuovere definitivamente tutti i messaggi.",
+ "trash_hint": "Puoi svuotare il Cestino per rimuovere definitivamente tutti i messaggi."
+ }
},
"email_viewer": {
"no_email_selected": "Nessun messaggio selezionato",
@@ -594,6 +602,10 @@
"permanent": "Elimina definitivamente",
"warning": "I messaggi verranno eliminati definitivamente e non potranno essere recuperati. Questa azione è irreversibile."
},
+ "permanently_delete_junk": {
+ "label": "Elimina spam definitivamente",
+ "description": "Elimina definitivamente i messaggi dalla cartella Spam invece di spostarli nel cestino"
+ },
"show_preview": {
"label": "Mostra anteprima testo",
"description": "Visualizza l'anteprima del messaggio nell'elenco"
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 481eccb6..19202061 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "完全に削除",
"permanent_delete_confirm_title": "完全に削除",
"permanent_delete_confirm_message": "このメールは完全に削除されます。この操作は取り消せません。",
- "permanent_delete_confirm_batch_message": "{count, plural, one {1件} other {#件}}のメールが完全に削除されます。この操作は取り消せません。"
+ "permanent_delete_confirm_batch_message": "{count, plural, one {1件} other {#件}}のメールが完全に削除されます。この操作は取り消せません。",
+ "empty_folder": {
+ "button": "フォルダを空にする",
+ "confirm_title": "フォルダを空にする",
+ "confirm_message": "このフォルダ内のすべてのメールが完全に削除されます。この操作は取り消せません。",
+ "confirm_button": "フォルダを空にする",
+ "junk_hint": "迷惑メールフォルダを空にして、すべてのメールを完全に削除できます。",
+ "trash_hint": "ゴミ箱を空にして、すべてのメールを完全に削除できます。"
+ }
},
"email_viewer": {
"no_email_selected": "メールが選択されていません",
@@ -594,6 +602,10 @@
"permanent": "完全に削除",
"warning": "メールは完全に削除され、復元できません。この操作は元に戻せません。"
},
+ "permanently_delete_junk": {
+ "label": "迷惑メールを完全に削除",
+ "description": "迷惑メールフォルダのメールをゴミ箱に移動せずに完全に削除する"
+ },
"show_preview": {
"label": "プレビューテキストを表示",
"description": "リストにメールのプレビューを表示"
diff --git a/locales/nl/common.json b/locales/nl/common.json
index c18bf510..5b457d50 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Definitief verwijderen",
"permanent_delete_confirm_title": "Definitief verwijderen",
"permanent_delete_confirm_message": "Deze e-mail wordt definitief verwijderd. Deze actie kan niet ongedaan worden gemaakt.",
- "permanent_delete_confirm_batch_message": "Deze {count, plural, one {1 e-mail} other {# e-mails}} worden definitief verwijderd. Deze actie kan niet ongedaan worden gemaakt."
+ "permanent_delete_confirm_batch_message": "Deze {count, plural, one {1 e-mail} other {# e-mails}} worden definitief verwijderd. Deze actie kan niet ongedaan worden gemaakt.",
+ "empty_folder": {
+ "button": "Map legen",
+ "confirm_title": "Map legen",
+ "confirm_message": "Alle e-mails in deze map worden permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.",
+ "confirm_button": "Map legen",
+ "junk_hint": "U kunt de map Spam legen om alle berichten permanent te verwijderen.",
+ "trash_hint": "U kunt de prullenbak legen om alle berichten permanent te verwijderen."
+ }
},
"email_viewer": {
"no_email_selected": "Geen e-mail geselecteerd",
@@ -594,6 +602,10 @@
"permanent": "Permanent verwijderen",
"warning": "E-mails worden permanent verwijderd en kunnen niet worden hersteld. Deze actie is onomkeerbaar."
},
+ "permanently_delete_junk": {
+ "label": "Spam permanent verwijderen",
+ "description": "E-mails uit de map Spam permanent verwijderen in plaats van naar de prullenbak te verplaatsen"
+ },
"show_preview": {
"label": "Voorbeeldtekst tonen",
"description": "E-mailvoorbeeld weergeven in de lijst"
diff --git a/locales/pt/common.json b/locales/pt/common.json
index e299ee26..3c18c23a 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -130,7 +130,15 @@
"permanent_delete": "Excluir permanentemente",
"permanent_delete_confirm_title": "Excluir permanentemente",
"permanent_delete_confirm_message": "Este e-mail será permanentemente excluído. Esta ação não pode ser desfeita.",
- "permanent_delete_confirm_batch_message": "Estes {count, plural, one {1 e-mail} other {# e-mails}} serão permanentemente excluídos. Esta ação não pode ser desfeita."
+ "permanent_delete_confirm_batch_message": "Estes {count, plural, one {1 e-mail} other {# e-mails}} serão permanentemente excluídos. Esta ação não pode ser desfeita.",
+ "empty_folder": {
+ "button": "Esvaziar pasta",
+ "confirm_title": "Esvaziar pasta",
+ "confirm_message": "Todos os e-mails nesta pasta serão permanentemente excluídos. Esta ação não pode ser desfeita.",
+ "confirm_button": "Esvaziar pasta",
+ "junk_hint": "Você pode esvaziar a pasta de Spam para remover permanentemente todas as mensagens.",
+ "trash_hint": "Você pode esvaziar a Lixeira para remover permanentemente todas as mensagens."
+ }
},
"email_viewer": {
"no_email_selected": "Nenhum e-mail selecionado",
@@ -594,6 +602,10 @@
"permanent": "Excluir Permanentemente",
"warning": "Os e-mails serão excluídos permanentemente e não poderão ser recuperados. Esta ação é irreversível."
},
+ "permanently_delete_junk": {
+ "label": "Excluir spam permanentemente",
+ "description": "Excluir permanentemente e-mails da pasta Spam em vez de movê-los para a Lixeira"
+ },
"show_preview": {
"label": "Mostrar Texto de Visualização",
"description": "Exibir visualização do e-mail na lista"
diff --git a/stores/email-store.ts b/stores/email-store.ts
index a6c198da..ed52cb4c 100644
--- a/stores/email-store.ts
+++ b/stores/email-store.ts
@@ -102,6 +102,7 @@ interface EmailStore {
renameMailbox: (client: JMAPClient, mailboxId: string, name: string) => Promise;
deleteMailbox: (client: JMAPClient, mailboxId: string) => Promise;
setMailboxRole: (client: JMAPClient, mailboxId: string, role: string | null) => Promise;
+ emptyMailbox: (client: JMAPClient, mailboxId: string) => Promise;
// Mock data for demo
loadMockData: () => void;
@@ -411,6 +412,7 @@ export const useEmailStore = create((set, get) => ({
// Get delete action preference from settings
const deleteAction = useSettingsStore.getState().deleteAction;
+ const permanentlyDeleteJunk = useSettingsStore.getState().permanentlyDeleteJunk;
// Determine accountId for shared folders
const selectedMailboxId = get().selectedMailbox;
@@ -418,6 +420,12 @@ export const useEmailStore = create((set, get) => ({
const currentMailbox = mailboxes.find(mb => mb.id === selectedMailboxId);
const accountId = currentMailbox?.isShared ? currentMailbox.accountId : undefined;
+ // If in junk folder and setting is enabled, permanently delete
+ const isInJunk = currentMailbox?.role === 'junk';
+ if (isInJunk && permanentlyDeleteJunk) {
+ forceDelete = true;
+ }
+
// If deleteAction is 'trash' and not forced permanent delete, try to move to trash mailbox
if (deleteAction === 'trash' && !forceDelete) {
// Find trash mailbox for the correct account
@@ -1287,6 +1295,35 @@ export const useEmailStore = create((set, get) => ({
}
},
+ emptyMailbox: async (client, mailboxId) => {
+ try {
+ set({ isLoading: true, error: null });
+ await client.emptyMailbox(mailboxId);
+
+ // Clear emails from local state if we're viewing this mailbox
+ const currentMailbox = get().selectedMailbox;
+ if (currentMailbox === mailboxId) {
+ set({ emails: [], selectedEmail: null });
+ }
+
+ // Update mailbox counters
+ set({
+ mailboxes: get().mailboxes.map(mb =>
+ mb.id === mailboxId
+ ? { ...mb, totalEmails: 0, unreadEmails: 0, totalThreads: 0, unreadThreads: 0 }
+ : mb
+ ),
+ isLoading: false,
+ });
+ } catch (error) {
+ set({
+ error: error instanceof Error ? error.message : 'Failed to empty folder',
+ isLoading: false,
+ });
+ throw error;
+ }
+ },
+
loadMockData: () => {
const mockEmails: Email[] = [
{
diff --git a/stores/settings-store.ts b/stores/settings-store.ts
index 3ba2435f..b90792cc 100644
--- a/stores/settings-store.ts
+++ b/stores/settings-store.ts
@@ -77,6 +77,7 @@ interface SettingsState {
// Email Behavior
markAsReadDelay: number; // milliseconds (0 = instant, -1 = never)
deleteAction: DeleteAction;
+ permanentlyDeleteJunk: boolean; // Permanently delete emails from junk/spam instead of moving to trash
showPreview: boolean;
emailsPerPage: number;
externalContentPolicy: ExternalContentPolicy;
@@ -154,6 +155,7 @@ const DEFAULT_SETTINGS = {
// Email Behavior
markAsReadDelay: 0, // Instant
deleteAction: 'trash' as DeleteAction,
+ permanentlyDeleteJunk: false,
showPreview: true,
emailsPerPage: 50,
externalContentPolicy: 'ask' as ExternalContentPolicy,