Nested tag toasts from drag-and-drop only showed the leaf name for
non-root tags, contradicting the comment above it and making two
same-named leaves under different parents (e.g. Personal/Receipts vs
Work/Receipts) indistinguishable in the toast.
The context menu's markAsRead handler was the one action left reading
the stale contextMenu.data instead of the live-refreshed
contextMenuEmail introduced alongside it, so it could act on outdated
email state while every sibling handler was already updated.
Previously tags where very much focused on color coding email and less about
adding additional information. They were also visualized in different ways in
different locations.
This commit gets rid of all "Color-coding" references, aligns visualization of
the tags across the whole project and tries to improve user experience of using
tags in general.
A search box is shown in the tagging control so the user can quickly search for
a tag if they have a huge (more than 10) amount of tags.
Adds the same "Forward as attachment" action from the message viewer's
overflow menu to the right-click context menu on a message row in the
list, right after "Forward". Reuses the handleForwardAsAttachment
handler and buildForwardAsAttachmentPayload helper introduced earlier
in this PR - no new logic, just threading the prop down EmailList ->
EmailContextMenu the same way onForward already is.
blobId is already present on list-row emails (EMAIL_LIST_PROPERTIES
includes it specifically for the existing drag-out-to-filesystem .eml
export feature), so this works without any additional fetch. The menu
item is disabled if it's ever missing, matching how other actions
degrade when their handler prop isn't supplied.
Reuses the email_viewer.forward_as_attachment translation key already
added (via a second scoped useTranslations("email_viewer") call,
matching this file's existing cross-namespace pattern for
email_viewer.color_tag) rather than adding a duplicate key under
context_menu - avoids touching all 24 locale files again.
Outlook-Web-style pinning: a context-menu Pin/Unpin action stores a
$pinned keyword on the message (plain IMAP-compatible flag, survives
other clients), and pinned mails stay at the top of the folder list
regardless of age, marked with a pin icon.
Ordering is done server-side via the hasKeyword sort comparator
(RFC 8621), applied consistently to the folder fetch, pagination and
the push-refresh so page windows stay stable. The client-side safety
sort in getEmails mirrors it, and sortThreadGroups keeps threads
containing a pinned mail on top so the client-side thread grouping
does not undo the order.
The new-mail notification in refreshCurrentMailbox now checks the
first non-pinned entry: with pinned mails on top, the newest mail is
no longer at index 0 and arrivals would never have notified.
The toggle reuses the color-tag pathway (routed keyword write for
unified views, in-place local patch), then refetches the first page
so the mail floats or sinks immediately. Search and unified views
keep their existing order.
Pin/Unpin strings are added to all 21 locales.
Add cross-account aggregate mail views and make group/shared (delegated)
accounts first-class in every aggregate view. (The unified mailbox, the "All
Mail" view, and "include group inboxes" already exist on main; this branch adds
the cross-account views and the shared-account correctness work.)
New views (admin-gated + per-user toggle, nested under Unified Mailbox):
- Cross-account "All accounts": All unread / All starred / All mail across every
connected account, including shared/group folders. Each list labels the source
folder of every message.
Source reference on aggregated emails (the core of the shared-account work):
- Replace the overloaded `accountId` with two explicit, always-set fields:
`sourceClientAccountId` (the login the mail is reachable through) and
`sourceAccountId` (the owning JMAP account). `accountId` stays display-only.
- Resolution is branch-free everywhere: pick the client by sourceClientAccountId,
pass sourceAccountId as the JMAP accountId (no-op for personal), read the
owner's mailbox list cached by JMAP id. No capability scan.
Shared/group-account correctness across all aggregate views:
- Route open (click + auto-fetch), thread/conversation open + reply-refresh,
mark read, star, move, delete (account-scoped trash), archive (owner-routed
createMailbox / fetchAccountMailboxes), and spam + undo via the source ref.
- Add accountId params to toggleStar / batchMarkAsRead / batchDeleteEmails /
createMailbox where missing.
- Fix local unread/total counter math for shared folders via emailInMailbox()
(matches namespaced shared ids and bare own ids).
- Keep the unified/cross virtual selection on background mailbox refresh (no
jump back to inbox after deleting in All Drafts/Junk).
Junk UX:
- In "All Junk" the spam action becomes "not spam" in the viewer, context menu,
and list hover icons; undo routes shared mail back to its own inbox.
Admin:
- Policy gates crossUnread/Starred/AllViewEnabled, each noting the matching
per-user toggle (allMailViewEnabled clarified too).
i18n / docs / tests:
- locales (19): cross-view labels + descriptions and hover not_spam, translated
in all shipped languages.
- FEATURES.md + README.md document the new views and group-account support.
- Tests for shared-account routing (single + batch + undoSpam), decoration, and
unified-selection preservation.
- Created demo emails with various states (inbox, sent, drafts, trash, etc.) in `emails.ts`.
- Added demo file nodes representing directories and files in `files.ts`.
- Implemented demo Sieve capabilities and scripts in `filters.ts`.
- Defined demo identities for users in `identities.ts`.
- Established demo mailboxes with permissions and counts in `mailboxes.ts`.
- Created a demo vacation response in `vacation.ts`.
- Introduced a comprehensive JMAP client interface in `client-interface.ts` to standardize interactions with the JMAP API.
- Add 'extra-compact' density level that hides avatars and preview
text, showing only sender and subject for maximum information density
- Add visual density preview in appearance settings showing a 3-row
email list mockup that reflects each density option
- Fix global font size setting only changing line height by moving
font-size from body to :root so rem-based Tailwind classes scale
- Apply extra-compact behavior to email list items, thread list items,
thread email items, and thread conversation view (EmailCard)
- Update email list virtualizer size estimates for extra-compact
- Add extra-compact translations for all 8 locales
Rename "List Density" to "Density" and apply spacing changes globally
instead of only to email list item heights.
- Replace hardcoded padding/gap values with CSS custom properties
(--density-item-py, --density-item-gap, --density-header-py,
--density-card-p, --density-sidebar-py) set via JS on :root
- Apply density-responsive spacing to email list items, thread views,
email viewer, sidebar, navigation rail, contacts, and calendar
- Use inline styles instead of Tailwind arbitrary value classes to
avoid Turbopack compilation hangs
- Rename listDensity → density in store, types, and components
- Add persist migration (v1 → v2) and onRehydrateStorage callback
- Update all 8 locale files with broadened labels/descriptions
- Add NavigationRail component (desktop vertical icon sidebar + mobile bottom tab bar)
- Add ConfirmDialog with promise-based useConfirmDialog hook for async confirmation flow
- Add WelcomeBanner onboarding component (one-time display, localStorage persistence)
- Polish login form UX (shake on error, TOTP slide animation, password visibility toggle, session expired banner)
- Add inline form validation with shake animation in email composer and contacts
- Add empty state patterns for contacts (no data vs no search results with contextual actions)
- Improve toast notification system with undo action support and typed durations
- Add WCAG AA prefers-reduced-motion media query, safe area insets, sr-only live regions
- Add template settings tab and keyboard shortcut integration
- Update all 8 locale translations
This release significantly expands internationalization support and adds comprehensive identity management features.
Internationalization (i18n):
- Add 5 new languages: Spanish, Italian, German, Dutch, Portuguese
- Expand from 3 to 8 total supported languages
- Redesign language switcher for better scalability (dropdown UI)
- Complete translations for all features across all languages
Identity Management:
- Multiple sender identities with per-identity signatures
- Sub-addressing support (user+tag@domain.com)
- Context-aware tag suggestions for sub-addresses
- Identity badges in email viewer and list
- Full CRUD operations for managing identities
Newsletter Management:
- RFC 2369 List-Unsubscribe support (one-click unsubscribe)
- HTTP and mailto unsubscribe methods
- Security validation prevents XSS attacks
- Two-step confirmation with persistent dismissal
Security & Accessibility:
- Dark mode email readability (intelligent color transformation)
- WCAG 2.0 Level AA color contrast compliance
- Comprehensive XSS prevention with validation utilities
- Unit test coverage for security-critical code (57 validation tests)
Testing:
- Add unit tests for validation utilities
- Add unit tests for email sanitization
- Add unit tests for color transformation
- Full test coverage for XSS attack vectors
- Add pagination support to search (previously limited to 50 results)
- Search now scoped to current mailbox/folder
- Support shared mailbox folders in search
- Display total results count (e.g., "50 of 1400 conversations")
- Add clear (X) button to search input
- Re-run search when changing folders during active search
- Remove unused sidebar screenshot
A modern, privacy-focused webmail client built with Next.js and the JMAP protocol.
Designed for Stalwart Mail Server.
Features:
- Full email operations (compose, reply, forward, threading)
- Real-time push notifications
- Dark/light theme support
- Mobile responsive design
- Keyboard shortcuts
- Drag-and-drop organization
- i18n (English/French)
- Security-first (external content blocked, HTML sanitization)