[0.4.0] - 2026-10-03
Added
-
Unread indicator on accounts: Sidebar accounts that have unread mail show a dot on the avatar and a bold name. In the narrow sidebar, the Unified Mailbox icon shows the same dot when any account has unread mail. Hovering an account includes the unread total. The indicator follows Settings, General, “Show unread counts next to accounts”.
-
Find all emails from this sender: The message and conversation right-click menus, and the address menu in the reader (From, To, CC, and Reply-To), can search for every message from that address. The query is filled into the search bar as a
from:filter. On a conversation, if the newest message was sent from your account, the filter uses the last To recipient who is not that account, so a reply does not search for yourself.
Changed
- Long recipient lists in the reader: To and CC in the message header
show display names instead of the full
Name <email>string. Lists of five or more people show the first three names and a “+N more” control that expands the rest inline, capped so a huge CC cannot push the body off screen. Hover still shows the full address, and the address right-click menu is unchanged. From and Reply-To still show the full address. The collapsed thread row uses display names as well.
Fixed
-
Dragging an attachment out renamed the file: The dropped file was saved with a unique id in front of the original filename. It now keeps the attachment’s name.
-
Compose spellcheck marked apostrophe words as misspelled: The editor stores a typed apostrophe as a curly quote, so correct words such as
com'è,don't, andco'were underlined. Those spellings are accepted. A typo such ascom'esuggests bothcom'èandcome, and one click on a suggestion replaces the word. -
Sidebar unread counts included archived mail: Custom-folder badges counted every unread label membership, including messages whose home mailbox is Archive. Starred badges counted flagged unread mail in Archive, Trash, and Spam. The Archive row itself showed an unread total. Those badges now skip archived mail. Account and Unified Mailbox totals were already limited to mail outside Archive, Trash, and Spam.
[0.3.9] - 2026-09-24
Added
-
Bulk mail-list context menu: Right-clicking a checked row while multiple messages or conversations are selected changes the menu (count header, both Mark as read and Mark as unread, no Reply/Print) and applies read, unread, delete, and spam to the whole selection. Right-clicking outside the selection still acts on that one row.
-
Focus next mail after archive: Archiving a message or conversation (hotkey or context menu) selects the next remaining row and moves keyboard focus there, so you can keep triaging without reaching for the mouse. The last item in the list falls back to the previous row. The message-list context menu now includes Archive as well.
Fixed
-
Mail list dates hid the year: older mail in the list used a short month+day stamp with no year, so a message from 2024 looked the same as one from this March. Dates from any year other than the current one now include the year. Today, yesterday, and this-week weekdays are unchanged.
-
Search filter panel hidden by the reader in split-bottom layout: the Add-filter popover was
position: absoluteinside the mail-list pane, which clips overflow. The reader sits later in the DOM and painted over the panel. The popover is nowposition: fixedto the trigger button, matching other overlay menus. -
Reply / Reply all opening an empty window or doing nothing: the compose payload was written to
localStoragewhile the window was activated through a Tauri event. The two webviews run in separate renderer processes wherelocalStorageis only eventually consistent, so the activation could land before the payload was visible; the compose page then bailed out silently and was either left hidden (nothing happened) or revealed empty by the main window. The payload now travels inside thecompose-activateevent itself and the same ready/activate handshake is used for prewarmed and fresh windows, so there is no side channel to race.localStorageis no longer used by compose at all (orphaned payloads could also fill the quota and make every later Reply fail). If preparing the message still fails, the window is revealed anyway with a visible error instead of a console line. Replies and forwards now re-read the full message from the database before quoting, so replying from the mail list no longer quotes a truncated plain-text snippet. -
Mail list preview showing “96”: Outlook/Word HTML embeds
<o:PixelsPerInch>96</o:PixelsPerInch>inside<!--[if mso]>conditional comments. Snippet stripping treated that comment as a tag ending at the first>, so the preview was often just “96” or started with it. Comments,<head>, and Office<xml>blocks are now dropped before the snippet is stored. Existing messages are rewritten on next launch.
[0.3.8] - 2026-08-13
Added
-
Include archived mail in Conversations search (Settings → General, off by default): when enabled, searching Conversations also matches messages in Archive, and opening a hit shows those archived messages in the thread. The Conversations list itself stays inbox-only.
-
Reply-To phishing warning: When a message has a Reply-To address whose domain differs from the From domain, the expanded message header shows the Reply-To line in an amber banner as possible phishing. Same-org subdomains (for example
mail.acme.comvsacme.com) and mailing-list mail are not flagged. -
Folder and Gmail label chips: Expanded messages show a location chip (Inbox, Sent, Archive, Trash, and so on; the physical folder on IMAP) plus Gmail user-label chips in that label’s Gmail colours. A message can stay in Inbox (or Archive) and carry several labels at once. IMAP shows the folder chip only; keyword/
$Labelsync is not implemented. Click a chip’s × to remove that label, or + / Tag to open a picker: check or uncheck labels, create one, and set its colour (Gmail’s own palette). The bulk bar has the same Label action for a multi-selection. Sidebar custom folders, unread counts, andlabel:/in:search use this membership rather than treating a custom label as the message’s only folder.
Fixed
-
Windows production update wiped the taskbar pin and default
.emlopener: the NSISPREINSTALLhook ran a full silent uninstall before copying the new files, even though the in-app updater already launches the installer with/UPDATEfor an in-place upgrade. That uninstall calledUnpinShortcut(which removes the taskbar pin) and tore down the.emlProgID, which invalidates the Windows hashed Default AppsUserChoice. Updates now skip that uninstall, leave existing Start Menu shortcuts alone, and register.emlunder a single ProgID (Synapse.EmailMessage). Machines already reset by an older update need a one-time re-pin and re-select of Synapse as the.emldefault. -
Sent-only threads in Conversations: Threads that only exist in Sent (outbound mail with no inbox message, or the inbox copy archived away) no longer appear in Conversations. Listing visibility now requires at least one inbox message; your sent replies still show inside mixed threads, and the Sent folder is unchanged.
[0.3.7] - 2026-07-31
Added
-
Reply-To on reply: When a message has an explicit Reply-To header, Reply and Reply All address that instead of From. Reply All also keeps Reply-To out of Cc so the same address is not duplicated.
-
Dark email body (Settings → Appearance, off by default): when a full Dark theme is active, HTML message bodies in the reader and EML viewer can use that theme’s background and foreground colors so plain mail matches the UI. Half-dark themes keep a light reader by design; branded emails that hard-code their own colors stay as light islands on the dark canvas.
Fixed
-
Account switch races (especially into IMAP/PEC): Switching from a Gmail Conversations view to another Gmail account worked, but switching to an IMAP or PEC account often left Conversations unselected (or the wrong folder’s mail on screen). Background soft-reloads from live sync could finish after the switch and overwrite the new account’s list with the previous account’s rows. Navigation now uses a generation token (same idea as search): stale loads and soft-reloads are discarded, and selecting an account clears the previous list immediately before mailboxes load.
-
Empty Conversations for IMAP/PEC: Gmail rebuilds
conversation_statsbefore notifying the UI; IMAP never did, so Conversations stayed empty even when Inbox had mail. IMAP now rebuilds stats after conversation-id backfill. On unlock, accounts that already finished sync with an empty Conversations list get a one-shot heal (backfill + rebuild). The primaryINBOXmailbox is never demoted to archive by a mis-tagged SPECIAL-USE attribute, which previously zeroed out the inbox+sent filter Conversations uses. -
PEC/Legalmail IMAP connection storm: Legacy PEC repair opened a full TCP→TLS→LOGIN for every PEC-flagged message, including ones already repaired, while IDLE also held a connection — three Legalmail accounts produced a continuous handshake loop. Repair now selects only rows that still need work and skips the IMAP fetch when a message is already repaired. IDLE also waits before reconnecting after a clean session end (provider disconnects were previously treated as immediate reconnect).
-
Gmail quota burn from overlapping syncs: Live poll and manual Refresh could sync the same Gmail account concurrently, each with its own quota limiter (two independent ~85% buckets). Incremental sync is now single-flight per account, and all Gmail clients for an account share one quota limiter. Soft-reload no longer runs twice at the end of every incremental sync (
progress doneandstatus idle), and empty IMAP syncs reportchanged: falseso they do not force a list refresh. -
Gmail attachments missing in the reader: Adding
reply_to_addrsto the Gmail message INSERT left a literal0(meant foris_pec) one slot too early, so every newly synced Gmail message was stored withhas_attachments = 0and the real attachment flag was written intois_pec. ThreadView never downloaded attachments for those messages even when the files were indexed. The INSERT is aligned again, a regression test asserts the stored columns, and migration 0078 backfillshas_attachmentsfrom the attachments index and clears bogus Gmailis_pecflags. -
Compose window sometimes never opened: Activating a prewarmed compose window was fire-and-forget (
emitTowith no check), and the ready signal could race ahead of the activate listener. A missed activation left the click with no visible window; a quick retry within the debounce window was dropped. Ready events are now filtered by window label, the activate listener is registered before TipTap signals ready, activation is confirmed (show/focus) from the main window with a visible-window fallback on failure, and the opened compose window is focused so it cannot appear behind the main window on Windows. -
Garbled Japanese/Chinese in mail-list previews: Conversation and message list snippets preferred the MIME plain-text part, which is often mis-decoded for CJK mail while the HTML part is fine (English stayed readable, JP/CN looked like mojibake). Snippets now prefer stripped HTML, skip
<style>/<script>contents so CSS does not leak into the preview, and a one-time startup backfill rewrites existing rows without loading full bodies in large batches. -
Read conversations stuck among unreads (unread-first): With “always show unread on top”, opening a thread cleared the unread badge but left the row in the unread block; a soft refresh could then sticky-append the selected (now-read) conversation between unread pages. Marking another conversation unread reloaded from SQL and resurfaced the same problem when
min_is_readstill counted unread archived siblings while the card’sunread_countonly saw inbox/sent. Opening a conversation now relocates that row into the read section immediately, soft-refresh re-partitions the unread/read boundary, andmin_is_readmatches inbox+sent only. -
Ordinary mail wrongly tagged as PEC: Detection treated any
.eml/ nestedmessage/rfc822attachment, loose subject words (“ricevuta”, “consegna”), and from-address substrings likepec.(e.g.spec.example.com) orlegalmailas certified mail. Classification now requires RFC 6109 signals (X-Trasporto/X-Ricevuta,postacert.eml/daticert.xml, standard subject prefixes, orposta-certificata@…). Existing false positives are demoted when the list or thread loads (or during account PEC repair) if the original RFC822 is available.
[0.3.6] - 2026-07-03
Changed
-
Inline image and attachment fetch speed (Gmail):
get_inline_imagesanddownload_message_attachmentsnow fetch remote Gmail attachment blobs concurrently (up to 6 simultaneousattachments.getcalls) instead of one network round-trip at a time. Image-heavy messages that previously took many seconds to tens of seconds in production perf logs should resolve in a fraction of the time. Small parts already inlined in the Gmail API JSON payload still resolve instantly; only remote blobs contend for the concurrency slots. Regular attachment downloads keep the same on-disk ordering and calendar-invite dedup behavior; only the network I/O is parallelized. -
Immediate HTML body render for CID inline images: messages whose HTML references
cid:inline images no longer defer the entire body behind a loading skeleton whileget_inline_imagesruns. The iframe renders immediately with unresolvedcid:refs safely rewritten to emptysrcattributes (avoidingERR_UNKNOWN_URL_SCHEMEin the sandboxed iframe). Once inline images arrive from the server, the body reloads once with the real images filled in, so text and layout are readable right away instead of waiting on a multi-second blank pulse.
Fixed
-
Single-word searches now return Gmail-like results: searching one bare word (“agostino”, “Ravi”, “rentalcars”, “mike”) previously ranked by BM25 relevance with a typeahead prefix star, which produced unusable results: a 2014 burst of notifications outranked all recent mail for “agostino”, “Ravi” was buried under ravioli/Ravine/RAVIAD prefix lookalikes, and “rentalcars” bookings came back in scrambled date order. Single bare words are now routed as correspondent lookups with three newest-first tiers: mail where the word matches a sender/recipient (including display names) comes first, messages that merely mention the word in the subject or body follow (so newsletters can no longer outrank the actual person), and prefix-only matches (which keep as-you-type recall while typing, e.g. “invoi” still finds “invoice”) are appended last. Within the people tier, results are ordered by affinity-weighted recency (interaction affinity times an exponential age decay): searching “mike” surfaces the Mike you actually correspond with (from your on-device sent/reply/open history) above a same-name newsletter sender whose mail merely arrived more recently and above threads that only cc a namesake, while a heavily-used group alias that merely displays the name (Xero bills via a finance list) cannot bury the real correspondent’s recent mail under its own months-old notifications. On a profile with no interaction history the tier falls back to pure newest-first. Applies to both the message list and conversation search, in all modes; quoted words, ID-like tokens, operators, and multi-word queries keep the relevance pipeline.
-
Stale search responses can no longer overwrite newer results: in the conversation view, a slower query fired while you were still typing (the debounce can emit “ra” on the way to “ravi”) could resolve after the final query and replace correct on-screen results with unrelated ones a second or two later. Conversation searches now carry a sequence token and only the latest search (or a subsequent mailbox load, e.g. after clearing the box) is allowed to paint. Short two-letter tokens also skip the prefix-star tier entirely — “ra*” expanded to a huge candidate set that cost seconds and returned pure noise, exactly the queries that lost the race; exact address and mention matches still work for deliberate 2-char searches like “jp”.
-
Outbox header under window controls: Refresh and Back to mail sat beneath the frameless window’s minimize/maximize/close buttons. The outbox header now uses the same right padding (
pr-28) as Address Book and thread view so those actions stay clear of the overlay. -
IMAP read/unread drift and connection storms on new accounts: Adding an IMAP account could open many simultaneous connections (initial sync, IDLE, attachment loads, flag updates) with no shared limit, which overwhelmed some providers and produced repeated “TCP connect timed out” errors. Failed
\SeenSTORE calls then left Synapse’s local read/unread state out of sync with the server (and with other clients such as Thunderbird). Message body fetches also usedRFC822/BODY[], which can implicitly set\Seenon some servers as a side effect of downloading. Synapse now caps concurrent IMAP connections per account (the IDLE session holds one slot), usesBODY.PEEK[]for sync and attachment fetches so flags change only on explicit user action, retries failed mark-folder-read operations through the operation queue, and reconciles\Seen/\Flaggedfor the 200 most recent messages in each mailbox on every incremental sync pass. -
Sent mail missing for IMAP/SMTP accounts: Outbound messages delivered successfully but never appeared in Sent (in Synapse or other IMAP clients such as Thunderbird) because plain SMTP does not file a copy on the server. After a successful send, Synapse now
APPENDs the raw MIME to the account’s discovered Sent mailbox (role = 'sent', with a"Sent"fallback). Gmail accounts are unchanged; the Gmail API already places sent mail in Sent as part of the send call. A failed Sent append is logged but does not fail the send, since delivery has already succeeded.
[0.3.5] - 2026-06-23
Added
-
Developer Mode performance logging (Settings → Developer, off by default): records phased timings for mail-read IPC operations (message hydration, conversation load, list, search, attachment and inline-image fetch) without logging email content. Backend entries capture
db_acquire_ms,query_ms,repair_ms, and sync-running context; frontend entries captureipc_msandnavigator.onLine. Logs are kept in an in-memory ring buffer (500 entries) and appended to{app_data}/logs/perf.jsonlfor sharing with support. The tab includes a live table (amber ≥ 500 ms, red ≥ 2 s), Refresh, Clear log, and Open log folder actions. -
Microsoft email accounts (OAuth2): Settings → Accounts → Sign in with Microsoft adds Outlook, Hotmail, and Microsoft 365 mailboxes via OAuth2 PKCE in the system browser. Sync uses IMAP XOAUTH2 against
outlook.office365.com; outbound mail uses SMTP XOAUTH2 againstsmtp.office365.com. Personal Microsoft accounts and work or school Entra tenants are supported. App registration is a public client (mobile and desktop) with redirect URIhttp://127.0.0.1and delegated Office 365 Exchange Online permissionsIMAP.AccessAsUser.AllandSMTP.Send; setSYNAPSE_MICROSOFT_CLIENT_IDat build time (no client secret). -
Local search personalization (privacy-preserving, on-device only): migration 0074 adds an
interactionsappend-only event log (opened,replied,search_result_clicked). Nothing is transmitted off-device. Opening a message, replying, or clicking a search result records a local event;search::affinityaggregates time-decayed sender and thread scores at read time (30-day volume half-life, 7-day recency half-life, saturated so one hyperactive contact cannot dominate). The feature reranker addssender_affinity,thread_affinity, andrecent_interactionfeatures; all are 0.0 on a cold profile and capped so they tilt ties toward people and threads you actually engage with without overriding clear lexical matches. A one-time ledgered backfill seeds initial affinity from sent-mail history so ranking is useful before new interaction data accumulates. -
Search quality harness (developer tooling against a real profile):
src-tauri/tests/search_eval.rsnow exercises the shippedrun_searchpipeline (FTS + semantic + reranker), not a standalone FTS query. Reports per-query and per-classnDCG@10,MRR,Recall@50, a raw-FTS baseline column, macro pipeline delta,p50/p95latency, lexical first-paintp50, Gmail-parityhit@1/hit@3, and index coverage sanity (messages vs FTS rows vs pending embeddings vs chunk count). npm aliases:npm run test:eval,test:eval:label(print ranked IDs for golden-set authoring),test:eval:tune(offline coordinate-ascent weight search),test:eval:train(offline logistic-regression trainer). SetSYNAPSE_EVAL_DBto yoursynapse.dbpath;scripts/run-eval.mjsauto-detects the dev profile on Windows. -
Optional learned reranker (off by default): infrastructure for a local logistic-regression model over the same 14 reranker features as the linear scorer. Enabled only when
SYNAPSE_LEARNED_RERANK=1and asearch_reranker.jsonmodel file is present (created bytest:eval:trainfrom on-device click labels). The hand-tuned linear model remains the default and always serves as fallback.
Changed
-
Hybrid mail search pipeline (relevance-first, local-only): end-to-end overhaul of message and conversation search aimed at Gmail-class precision and recall on large mailboxes.
- Normalized body indexing (migration 0073): FTS now indexes
body_index(HTML, quoted replies, and signatures stripped) instead of rawbody_text. New mail is normalized at write time; a ledgered background backfill recomputes existing rows incrementally. Display bodies are unchanged; snippets use a best-window over rawbody_text. - Intent-aware retrieval: queries are classified (
exact,person,subject,filter_only,natural_language,typo_prone) and routed with tuned RRF leg weights, semantic skip rules, and recency half-lives. Filter-only queries use SQL only; person/address queries skip semantic; exact and quoted-phrase queries suppress recency shuffling. - Precision gates: semantic hits below cosine 0.35 are dropped unless FTS returns fewer than 10 candidates; trigram fallback activates only on weak FTS; short-query containment filter reduces semantic noise.
- Feature reranker: top-50 candidates rescored in < 5 ms on BM25, semantic cosine, phrase proximity (NEAR/8), exact sender/subject/ID boosters, thread density, intent-adaptive recency, starred flag, and local affinity. Recency is applied once (no double shuffle).
- Conversation thread ranking: thread score blends match volume with
recency using the newest matching message’s date; pure
from:/to:/cc:/filter queries order threads newest-first and surface the matched message on each card (not the thread’s latest unrelated reply). - Sender and domain lookups: bare domains and emails (
crypto.com,alice@example.com) and purefrom:/to:/cc:queries without free text fetch candidates newest-first and skip semantic search, matching Gmail’s behavior when many messages share a sender. Typeahead prefix matching (invoi→invoi*) no longer appends a trailing*to complete domains or email addresses (fixes flooded candidate pools on domain searches). - Match reasons: each result carries explainable labels (sender, subject, body, phrase, exact_id, thread, semantic, fuzzy, filter, frequent contact).
- Normalized body indexing (migration 0073): FTS now indexes
-
Progressive hybrid search in the UI: hybrid mode now paints FTS-only results in tens of milliseconds via a
search://partialevent, then returns the semantic-refined final ranking. Request IDs guard against stale partials or superseded finals clobbering a newer query. Keystroke debounce reduced from 350 ms to 140 ms for as-you-type feel without extra races. -
Search startup and cache: the embedding model runs one throwaway inference at startup to avoid ORT cold-start on the first query; a 256-entry session query-embedding cache makes repeated or refined searches cheaper. Settings → AI → Search Index Health reports live FTS/embedding row counts, pending embeddings, HNSW readiness, rolling
p50/p95search latency, and query-cache size. -
Fast vector index (HNSW): Migrated from
instant-distancetohnsw_rs(stable-Rust HNSW). The ANN graph is keyed byVectorIndexslot; stale chunk IDs are tombstoned on message re-embed, and online inserts keep the graph current without full rebuilds. Graph persistence writeshnswlib_rs_v1.hnsw.data/hnswlib_rs_v1.hnsw.graphand a tinyhnswlib_rs_v1_metasidecar to the cache directory; on startup the graph is loaded from disk in seconds rather than rebuilt from scratch. The oldhnsw_cache.binformat is no longer read. Background priority still duty-cycle throttles bulk catch-up inserts.
Fixed
-
“Database is locked” during parallel account syncs: SQLite allows only one writer at a time; concurrent initial syncs (message inserts, embedding backfill, FTS rebuild, progress updates) and per-message attachment indexing tasks contended on the lock and surfaced
SQLITE_BUSYorSQLITE_BUSY_SNAPSHOT. Added a process-global write queue (single-permit async semaphore) in the db module: writers acquire the permit beforebegin()or a standalone execute and release aftercommit(). Fetch and parse work stays parallel; only the short write sections serialize. Routed sync batches (IMAP, Gmail), background writers (embeddings, FTS chunks, conversation/contacts backfill, reminders, sync progress), drafts, and attachmentupsert_attachmentthrough the queue. Long startup maintenance (PRAGMA optimize, scope FTS optimize) holds the permit viagated_write_retryso it cannot commit mid-transaction against an open gated writer. -
Reminders cleanup slow query with plugin disabled: After each sync,
clear_account_remindersran aSELECT EXISTSwith anORacrossreminder_kindandreminder_dismissed_at, which forced a full scan ofconversation_stats(~1 s per account) even when Reminders was off and no rows carried reminder state. The existence check now uses aUNIONso SQLite can walk the existing partial indexes on each branch separately. -
Conversation search for
from:/to:/cc:and bare domains: Person and address queries now use FTS only (semantic search is disabled). Thread ranking folds recency into the score so a recent small thread is not buried by an old high-volume one; pure sender, filter, and bare domain/email lookups (crypto.com,alice@example.com) order newest-first in both the message list and the conversation view. Search result cards show the matched message (sender and date), not the thread’s latest reply, so results no longer look unrelated or out of order. Bare-domain conversation search previously ranked by BM25 and capped candidates before reranking, which let old mail that repeated the domain in its subject bury and evict the newest receipts; it now uses the same date-ordered sender lookup path as the message search.
[0.3.4] - 2026-06-04
Added
- Sidebar unread badges and toggles (Settings → General, all on by default):
- Show unread counts next to accounts: per-account totals and the Unified Mailbox total (hidden when the count is zero).
- Show unread counts next to custom folders: unread totals on user-created folders (combined by folder name in unified mode; hidden when zero).
- Show unread count next to the selected default folder: unread total on the active Default Folders row only (Conversations, Inbox, Sent, Starred, Archive, Trash, Spam, Drafts, etc.; role totals combined in unified mode; hidden when zero).
- Backend commands
custom_folder_unread_counts,default_folder_unread_counts,conversations_unread_counts, andstarred_unread_countsfeed the sidebar; counts refresh after sync and read/unread actions.
Fixed
-
Sidebar unread badges not updating live: Unread numbers could stay stale until a folder section was collapsed and expanded. Badge rows now re-render when counts change (reactive snapshot and
{#each}keys tied to unread totals). -
Default-folder unread badge: Active-state detection now follows
listSource, so the selected-folder badge works for Conversations and role folders (including Archive), not only when a non-reactive local role variable happened to match. -
Instant unread badge feedback: Mark read/unread, new mail, and conversation mutations apply optimistic sidebar deltas (0 to 1 and back) before the database refresh; custom-folder updates no longer wait on a 1s throttle for user actions.
-
Per-account conversation loading: Opening a thread from a specific account could feel much slower than in the unified mailbox because SQLite chose the wrong indexes. Thread fetch (
get_conversation_messages) now usesINDEXED BY idx_messages_conv_listingso the planner does not scanidx_messages_conv_dateacross the whole mailbox to satisfyORDER BY date. Conversation summary refresh for a single account now usesINDEXED BY idx_messages_account_conv_listing(account-first covering index from migration 0023) instead of the conversation-first index, avoiding a heap-fetch ofaccount_idon every row.
[0.3.3] - 2026-06-04
Added
-
Unarchive in Archive: When viewing the Archive folder, right-click a message for Unarchive in the context menu, use the bulk action bar Unarchive button for multi-select, or press
eto move selected mail back to Inbox. Gmail accounts re-apply theINBOXlabel; IMAP accounts move to the Inbox folder. Uses the existingbulk_move_messagespath (same as “Not spam”). -
One-click unsubscribe: Messages with
List-Unsubscribeheaders show an Unsubscribe action in the reader toolbar and message context menu. Synapse tries RFC 8058 one-click POST (withList-Unsubscribe: One-Click), falls back to HTTPS GET when POST is not allowed, then sends amailto:unsubscribe if present. If automation fails, the list URL opens in your default browser so you can finish there. The button appears for bulk/list mail detected at sync (has_list_headers); PEC messages are excluded. -
Embedding processing priority (Settings → AI): choose Background (default) or Maximum performance. Background duty-cycles ONNX embedding work and fast vector index (HNSW) builds to about 10% average GPU/CPU utilization (sleep
9×work duration after each sub-batch or HNSW build phase) so long backfills, ANN graph construction, and incremental indexing stay responsive. Maximum performance removes throttling so indexing finishes as fast as possible. The choice is stored inlocalStorageand pushed to the Rust backend viaset_embedding_priorityon startup and when changed.
Changed
-
On-device LLM runtime (
llama-cpp-4): Replacedllama-cpp-20.1.146 withllama-cpp-40.3.0 (May 2026 llama.cpp, Vulkan + OpenMP only;mtmdanddynamic-linkdefault features disabled). The bundled engine was too old for bartowski Qwen3.5-4B GGUFs (qwen35hybrid SSM/DeltaNet); the new runtime matches current converter expectations. Inference code inai/inference.rswas adapted: chat template viaget_chat_template/apply_chat_template(no OpenAI-compat Jinja kwargs in -4), empty thinking-block suffix for Qwen3.5 whenenable_thinkingis off,token_to_bytesdecoding, and panic-safe GBNF grammar init. -
Mail list IPC payload: List and pagination commands (
list_messages, unified folder/role/starred queries) now use a slim SQL select instead of full message bodies.body_htmlis omitted (NULLon the wire);body_textis truncated to 500 characters, enough for the list preview. Single-message fetch (get_message) and the final search top-50 still return full bodies for the reader. -
Sender avatar computation: Gravatar/Libravatar URLs, identity parsing, and fallback colors/aurora meshes are memoized per email for the app session, so scrolling or re-rendering the mail list does not repeat MD5 and gradient work for the same sender.
-
Search candidate RAM: Hybrid, FTS, and semantic search now load slim candidates (
body_htmlomitted,body_texttruncated to 4000 characters) for up to ~200 rows per leg, then hydrate full bodies in one query for the final top 50. Cuts peak search RAM from hundreds of MB (full HTML across hundreds of candidates) to a few MB plus a single 50-row fetch. -
Thread body lazy loading: Opening a conversation loads slim message rows (no
body_html,body_texttruncated to 500 characters for collapsed previews). Expanding a message fetches the full body viaget_messageonce per message, so long threads no longer put every body in the JS heap upfront. -
IMAP post-sync concurrency: After each sync batch, attachment indexing and chunk embedding tasks share bounded semaphores (4 permits each) instead of spawning unbounded work. Raw message bytes are passed as
Arc<Vec<u8>>so attachment tasks clone a reference, not a full copy of the RFC822 payload. -
Embedding backfill ID pagination: Auto-backfill, manual backfill, and rebuild-search-index no longer load every pending message ID in one SQLite query (which could return 400k+ rows and trip sqlx slow-statement warnings). They use
count_pendingfor the progress total and cursor-paginatedfetch_pending_ids_pageso each page stays bounded to the backfill chunk size. -
Chunk vector cache incremental load: When the on-disk vector cache is behind
vec_message_chunksbut still valid, startup merges only new chunk rows (WHERE id > max_cached_id) instead of always scanning all embedding BLOBs in 10k-row pages. Matches the existing incremental path for legacyvec_messages. -
Embedding batch write throughput: Phase 2 of the chunk batch pipeline now deletes existing rows with a single
DELETE … WHERE message_id IN (…)per 999 IDs instead of one statement per message. Phase 4 wraps all chunk INSERTs andUPDATE has_embedding = 1statements in a single transaction, reducing WAL flushes from ~2 000 per 500-message batch to one. In-memory vector cache updates and event emissions are deferred to after the commit so they only reflect durable data. -
Chunk overlap reduction: Default chunk overlap reduced from 50 words to 25 words (step 200 → 225). Generates ~10% fewer chunks per message, directly reducing ONNX inference work during backfill with negligible recall impact.
-
HNSW build memory: HNSW graph construction documents and tightens the transient vector copy lifecycle (
points/valuesbuilt with exact capacity, read lock released beforeBuilder::build, vectors moved in and freed on return). Steady-state vector-index deduplication (mmap, quantization) remains deferred. -
Frontend dependency upgrades: Tailwind CSS 3 → 4 with the official upgrade path (
@tailwindcss/postcss, CSS-first theme inapp.css, removedtailwind.config.tsandautoprefixer). SvelteKit, Svelte, TypeScript, Vite, Vitest, and@sveltejs/vite-plugin-svelte(v6, kept on Vite 6) bumped to current releases. TipTap compose packages updated to 3.23.x. Tauri JavaScript packages (@tauri-apps/api, CLI, plugins) aligned on the 2.11 line. -
Icons and UI libraries:
lucide-sveltereplaced by@lucide/svelte(package rename); imports updated across the app. Removed unusedbits-ui. -
Rust and Tauri dependency upgrades:
tauripinned to 2.11 to match@tauri-apps/api. Major crate bumps:sqlx0.8 → 0.9 (dynamic SQL audited withsqlx::AssertSqlSafe),oauth24 → 5 (OAuth client builder updated inauth),thiserror1 → 2. Lockfiles (package-lock.json,Cargo.lock) regenerated.
Fixed
-
Qwen3.5 model load failure:
llama_model_load: missing tensor 'blk.32.ssm_conv1d.weight'when loading a complete GGUF was an engine/GGUF version mismatch, not a truncated download. Resolved by thellama-cpp-4migration above. -
Model download on Windows: Replacing an in-use GGUF during download could fail with “Access is denied” (os error 5) because llama.cpp memory-maps the file.
load_modelnow optionally deletes corrupt files before releasingLOAD_LOCK;download_modelwaits on that lock and retries the.tmprename with backoff. Truncated downloads are rejected by size check; failed loads remove the file and emitmodel://corruptfor Settings. -
Model download UI: Progress is restored when reopening Settings if a download is still running (
is_model_downloading). A second Download click no longer starts parallel downloads (IS_DOWNLOADINGguard andalready_runningprogress events). -
Onboarding window drag: The Welcome screen could not move the frameless main window. Added a top drag strip with
data-tauri-drag-regionand the samestartDragging()mousedown handler used in Settings and the EML viewer. -
docs/search.mdresource budget: Corrected the vector-cache row; the app uses a 256-entry query-embedding cache (clear on overflow), not a 256 MB LRU vector cache. -
src-tauri/Cargo.tomlcorruption: Section-separator comment lines had been damaged by repeated mis-encoding (mojibake), inflating the file to about 4.9 MB. Restored clean ASCII comments; Cargo reads a normal-sized manifest again. -
Reminders plugin scan storm: Enabling Reminders (Settings → Plugins) or saving its settings could start multiple overlapping full-account scans, each running a separate ~1 s SQL query per conversation (
LIMIT 2on message bodies). Disabling the plugin only cleared flags in a new task; earlier scans kept running and spammed slow-query logs. Scans now go through a per-account coordinator (one active scan, generation bump aborts superseded work, loop stops when disabled). Message data is loaded in chunks of 150 conversations via a window-function query with an 8 KBbody_textprefix and mailboxINfiltering instead of per-thread JOINs. -
Reminders smarter filtering: At sync, Gmail and IMAP now persist bulk-mail header signals (
Auto-Submitted,Precedence,List-*, noreplyReply-To,Feedback-ID) on each message. The scanner uses those flags before body heuristics, matches a shipped offline ESP/transactional domain list, expanded subject and HTML unsubscribe patterns, and skips newsletter-only threads where you never sent a reply. -
synapse://deep links on Windows: Pasting or opening URLs such assynapse://email/503804in a browser did nothing because Windows had no handler registered (browsers treated the string as a search query). The NSIS installer now writes thesynapseURL protocol underHKCR, release builds re-register only when the installed executable is not already the handler, andtauri-plugin-single-instanceforwards links to the running app instead of spawning a second instance. Callingregister_all()on every Windows startup was removed: it overwrote the installer handler with a dev binary that loadshttp://localhost:1420, so cold-start links from Edge failed when Synapse was closed and Vite was not running. -
Search bar clear button: Clicking the X to clear the query no longer leaves the search field focused; keyboard focus moves away so the bar returns to its unfocused appearance.
[0.3.2] - 2026-05-28
Added
-
Collapse previous messages in email chains: Long reply threads in the message body can be collapsed into a single Previous message or Previous messages (N) disclosure. The control uses native HTML
<details>inside the sandboxed email iframe (no scripts), so it works with the existing security model. Nested quotes from Gmail, Apple Mail, Outlook, Mailbird, and similar clients are flattened into one block. A General setting controls whether quoted chains start collapsed or expanded (default: collapsed). The iframe resizes when you expand or collapse the block. -
Save all attachments: When a message has more than one attachment, the attachment right-click menu includes Save All Attachments, which opens a folder picker and copies every downloaded attachment into the chosen folder in one step. Duplicate filenames get a numeric suffix before the extension (for example
photo_2.jpg).
Changed
- Full-screen views hide the sidebar: Settings, Address Book, and Outbox no longer show the accounts and folders sidebar; those panels use the full window width. The sidebar stays mounted but hidden so account avatars do not flash when you return to mail.
Fixed
-
Search when leaving Settings: Returning from Settings to the mail view could throw
Cannot access 'debounceTimer' before initializationwhen a search was still active.SearchBarnow declares debounce state before store subscriptions so remounting the search bar is safe. -
Compose quoted email selection: Previous messages in a reply or forward are shown in a sandboxed iframe inside the compose window. That iframe had
pointer-events: noneanduser-select: none, so you could not select or copy text from the quoted chain. The iframe is now interactive: you can select and copy quoted text, and http/https links open in the system browser. The surrounding compose layout and CSS are unchanged; deleting the whole quoted block still works from the attribution line or with Backspace from the line above. -
Gmail custom folder sync: Custom Gmail labels (user-created folders) could stop updating after the initial sync: counts and latest dates in Synapse fell behind Gmail because incremental sync only routed new mail to system folders (Inbox, Sent, Drafts, Trash, Spam) or Archive and ignored custom label ids. Label add/remove history for custom folders was also ignored. Migration
0062_gmail_label_id_on_mailboxes.sqlstores each mailbox’s Gmail label id; incremental routing and history handling now move messages into and out of custom folders. A one-time reconcile on the first sync after upgrade reassigns messages that were misrouted to Archive so existing folders catch up with Gmail.
[0.3.1] - 2026-05-27
Added
- Mail list wide layout: Added a “Wide Layout” option for the mail list in Split Bottom mode. When enabled, the sender and subject are displayed on a single line to better utilize horizontal space. When combined with “Hide message preview”, the list enters a Compact Mode where the row padding and avatar size are reduced, allowing significantly more rows to fit in the same vertical space. The setting is automatically disabled in Three Column mode to preserve legibility.
- Durable outbox (SMTP and Gmail): Every send is written to the outbox
before any network call, with
draft_jsonand opportunisticraw_mimestored for retries. Failed sends move throughqueued/failedwith exponential backoff; after five attempts they escalate toneeds_attention. Migration0061_outbox_durability.sqladds the new columns and index. IMAP idle and Gmail poll call a unifiedretry_outboxloop; MIME can be rebuilt fromdraft_jsonwhen needed. - Outbox UI: Sidebar entry and Outbox view list stuck messages with
Retry now, Edit & resend, Discard, and expandable error details.
A bottom banner in the main content area reports pending or needs-attention
counts and links to the Outbox view (
outbox://pending-countandoutbox://needs-attention-countevents). - Compose guards on send failure: After a failed send, the compose window flushes the draft session and prompts before close so unsent content is not lost.
- Outbox tests: Rust unit tests in
outbox/mod.rscover enqueue, retry selection, backoff, escalation, counts, force retry, discard, list/get, MIME reconstruction, and legacy row handling. - Conversation archive tests: Rust integration tests in
conversations.rscover thread view and listing when messages are in inbox, sent, archive, or mixed roles (fully archived threads hidden from the list; mixed threads show only non-archived messages).
Changed
-
Reply with AI: Opens in reply-all mode when the message has multiple recipients (the same condition that shows the Reply all button). Previously it always opened a single-recipient reply.
-
Outbox banner placement: The delivery-status strip is no longer a full-width fixed overlay; it sits at the bottom of the main content column so it does not cover sidebar items such as Settings.
-
Outbox discard confirmation: Discard in the Outbox view uses the app
ConfirmDialog(destructive style) instead of the browserconfirm()dialog. -
Conversations view and archived mail: Threads in the Conversations view now include only inbox and sent messages. Archived messages in the same conversation are omitted from the thread and from list summaries (message count, snippet). Conversations whose messages are all archived stay hidden from the list (
non_archive_ct = 0), unchanged from before.
Fixed
-
Stuck embedding backfill: Fixed an infinite loop in the embedding backfill process where messages (especially those with mostly whitespace bodies) would remain in the “pending” state despite multiple attempts. The
has_embeddingflag is now the single source of truth for processed messages, ensuring ineligible or failed messages are correctly excluded from future counts. Also resolved a bug in chunk ID retrieval duringINSERT OR IGNOREconflicts. -
Search mode toggle: Changing the search mode (FTS / AI / Hybrid) in the search bar now correctly re-triggers the search when results are already visible. Previously, the toggle only updated the UI state without refreshing the results.
-
Outbox rows without
draft_json: Messages queued before migration 0061 could not be retried or opened for edit becauseraw_mimeanddraft_jsonwere missing. Retry now and Edit & resend reconstruct aDraftMessagefrom the stored address and body columns, back-filldraft_json, and proceed (attachments on those rows cannot be recovered). -
Outbox force retry: Retry now resets
attemptsand clears backoff soneeds_attentionrows get a full retry cycle;queuedrows are always eligible for the retry loop regardless of prior attempt count. -
Compose insert link: After applying a link to selected text, the caret now moves to the end of the linked text (outside the link mark) instead of leaving the selection highlighted. Typing after Apply no longer extends the link or inserts an extra space inside it.
-
Search bar clear-all button: With many filter chips or a narrow mail list column, the X to clear the entire query sat inside the horizontally scrollable chip row and could only be reached by scrolling. The clear button now sits outside the scrollable region so it stays visible at the trailing edge of the search field.
-
Gmail thread conversation lookup: Resolving an existing conversation by Gmail native
threadIdcould take many seconds on large mailboxes because the index only coveredgmail_native_thread_id, so SQLite scanned every account sharing that thread before applyingaccount_id. Migration0058_gmail_native_thread_id_account_index.sqladds a partial composite index on(account_id, gmail_native_thread_id)whereconversation_id IS NOT NULL. Migration0060_gmail_thread_account_covering_index.sqlextends that index withdate,id, andconversation_idsoORDER BY date ASC, id ASC LIMIT 1is satisfied from the index alone (no heap sort or table lookup).
[0.3.0] - 2026-05-15
Added
- Deferred sending with undo: After clicking Send, Synapse waits a configurable grace period (default 5 s) before making the actual send call. A toast with an Undo button appears during the wait; pressing Undo or Ctrl+Z cancels the send and reopens the compose window with the draft intact. The delay can be changed (Off / 5 s / 10 s / 20 s / 30 s) in Settings → General → Undo send delay. Matches Gmail’s “Undo Send” UX. Setting the delay to Off restores immediate send behaviour.
- Archive and trash undo: Archiving or moving messages or conversations to Trash (keyboard shortcuts, bulk bar, and context menu) hides them in the list immediately, shows a success toast with an Undo action, and runs the remote mutation after a short grace period. Undo or Ctrl+Z cancels the pending action and restores the rows; permanent deletes from Trash are unchanged (immediate, no undo).
- Pending send banner (IMAP / SMTP outbox): When a send is stored in the
outbox with status
failed(for example after going offline), the main window shows a persistent bottom banner with the count of messages waiting to send and that they will retry automatically when connected. The count is loaded on startup and kept in sync via theoutbox://pending-countevent after each sync cycle and when a send attempt fails. - Automated tests for offline resilience: Unit tests for the operation queue
(enqueue, drain, max attempts), SMTP outbox retry,
network_error_to_string, and Vitest coverage fortoastnetwork-aware helpers. - Reader link hover tooltip: Hovering a link in the HTML message body (and
in the PEC envelope iframe when shown) displays a floating tooltip with the
full
hrefstring. The tooltip is drawn in the outer window so it is not clipped by the email iframe.
Fixed
- Keyboard shortcuts with Ctrl+letter:
normalizeKeyCombonow uppercases the base key when Ctrl, Alt, or Meta is held so the produced combo matches preset maps (e.g.Ctrl+Zfor undo, Mailbird’sCtrl+N/Ctrl+R, etc.). Browsers reportevent.keyas lowercase for these chords, which previously prevented the reverse lookup from firing. - Gmail send while offline: Offline send failures could surface as a raw
error sending request for url (https://gmail.googleapis.com/...)string. Transport-style reqwest failures (including send-phase errors whereis_connect()is false) are now mapped to the sameNETWORK_UNAVAILABLEpath as other commands, and the compose window shows the offline or server-unreachable message instead of the URL. - Gmail conversation threading: Synapse grouped conversations only from
RFC headers (
References,In-Reply-To,Message-ID), so threads that Gmail kept together (e.g. weak or missing reply headers) could appear as several separate conversations. Assignment now falls back to Gmail’s nativethreadIdwhen headers do not link a message to an existing conversation, and migration0056_rethread_gmail_split_conversations.sqlclearsconversation_idfor messages in split native threads so the next startup backfill reunifies them. - Mail list multi-select shortcuts: Read/unread, archive, and delete keyboard shortcuts only affected the focused row. They now apply to every selected conversation or message, consistent with the bulk action bar.
- Mail list: J/K navigation and focus ring: Clicking a row moved browser
focus to that element, but J / K only updated the selected row in state,
so the previously clicked row could keep the
:focus-visiblering (two blue lines) while another row showed the selected styling. After each nextItem / prevItem navigation, the handler now focuses the newly selected.mail-row(withpreventScrollso scrolling stays unchanged). - Message hybrid search (short queries): For three or fewer plain tokens, the semantic leg could behave like OR and surface messages that matched only part of the query. Semantic candidates are now post-filtered so every token appears in the subject or body before reciprocal-rank fusion.
- Conversation view and custom folders: Threads whose messages live only in
user-created folders (
roleunset) were still listed because stats and scope treated those mailboxes like inbox/sent. Listing, search, and hydration now align with standard folders only (inbox, sent, archive); migration0057_exclude_custom_folder_conversations.sqlrecomputesconversation_statsand replaces the triggers sonon_archive_ctcounts only inbox and sent. - Gmail bulk move to a custom folder: Moving to a user label called
modify_messagewith an emptyaddLabelIdslist while still removing the source label (e.g.INBOX), so messages left the inbox in Gmail but never received the new label. Custom targets now resolve the API label id by display name, and the “skip remote update” guard no longer fires when the target is a custom folder but there is nothing to remove from the current label set. - Unified mode: custom folders: The sidebar shows one row per custom
folder name, but opening it loaded only the first account’s mailbox id, so
messages in a same-named folder on another account (e.g. after a cross-account
move) never appeared in the mail list. Unified selection now lists messages
from every mailbox with that name via
list_unified_messages_by_folder_name, with matching pagination, refresh, soft-reload, and live-sync handling. - Local LLM model download (Windows): Downloading the optional Qwen GGUF could fail with Cannot create model file and Windows error 1224 (file has a user-mapped section open) when a previous or partial file was already loaded at startup. The downloader now skips work when the model is already loaded, unloads before overwriting so llama.cpp releases its memory map, streams into a temporary file, and renames it into place so interrupted downloads do not leave a truncated GGUF on the final path.
Changed
- Theme: “Default” renamed to “Simple”: The first light theme is now
labelled Simple (id
simple) to reflect that it is no longer the application default. Existing preference values pointing todefaultshould be migrated tosimple. - Theme: Aurora Sky: Hues shifted from the cyan/teal range (158–198) to the blue range (190–215) and lightness reduced by ~4–8 points throughout, giving the sidebar, accents, and aurora mesh a noticeably deeper, bluer look.
- Mail list multi-select: After a bulk action finishes, the row selection is cleared and multi-select mode turns off. This applies to toolbar actions (including read/unread toggle and export, which previously left selection active) and to the matching mail shortcuts when several rows are selected.
- Conversation FTS ordering and cap: The bounded FTS subquery now selects
candidates ordered by BM25 (
rank), and the outer query keeps that order so results are relevance-ranked instead of docid (insertion) order. The candidate cap is lowered from 10 000 to 2 000 to reduce sort work on broad queries while still yielding enough rows after mailbox filters. - Conversation thread-level rerank: Rank-decayed thread scoring now includes at most three matching messages per thread, so very large threads with many weak body hits no longer outscore smaller threads with a stronger best individual match.
- Gmail attachment errors offline: When Gmail attachment download fails
because the network is unreachable (connection, timeout, or send-phase
error sending requestfailures), the app shows a short offline / connectivity toast instead of a rawreqwestURL error. Other failures still surface the detailed error string. - Hybrid search reranking: Subject signals are tiered (contiguous phrase, all tokens in any order, partial) with stronger boosts for an exact phrase in the subject line, including when passing dense query text into exact-match feature computation so phrase matches can outrank recency-weighted partial hits.
[0.2.9] - 2026-05-13
Fixed
- Search index health (Settings):
COUNT(*)on FTS5 virtual tables scanned the full inverted index (tens of seconds on large mailboxes). Diagnostics now count rows via the FTS5_docsizeshadow tables instead, which is effectively instant while still reflecting indexed documents. - Search index health (Settings): The diagnostics card could stay on
“Loading diagnostics…” when Settings → AI opened while the embedding model was
still initialising and the SQLite pool was busy with mail sync. The UI now
defers the
get_search_diagnosticsinvoke until embeddings reportready(same guard as the pending-count refresh), the main DB pool uses an explicit connection acquire timeout so a starved pool fails fast instead of hanging forever, and the pending-embeddings diagnostic count usesTRIM(body_text)so it can use the existing partial index. - Conversation hybrid search latency: Natural-language queries could spend
15+ seconds in the FTS leg because common function words inflated posting-list
intersections and the planner could not cap FTS iteration before applying
account and mailbox filters. Plain Italian and English stop words are now
omitted from the compiled FTS
MATCH(but still included in dense text for the semantic leg), and conversation FTS uses a bounded inner subquery so the index stops after a fixed candidate cap before joining tomessages. - Mark conversation unread: Mark-unread from the conversation list (e.g. Ctrl+U with the Mailbird shortcut preset) previously flagged every message in the thread as unread. It now marks only the latest message per conversation, so the thread shows a single unread instead of inflating counts across the whole conversation.
- Gmail attachments (e.g. PDFs with Content-ID):
has_attachmentstreated every MIME part with aContent-IDas an inline-only asset, so real attachments such as PDFs that still carried a Content-ID never surfaced in the reader or paperclip indicator. Detection now ignores only CID parts that are actuallyimage/*; migration 0055 back-fillshas_attachmentsfor affected rows, and a successful attachment download corrects the flag when the indexer had skipped the part.
[0.2.8] - 2026-05-11
Added
- Low-memory mode for embeddings: On machines with 16 GB of RAM or less,
Synapse now automatically detects available system memory at startup and
activates a conservative embedding pipeline:
- ONNX Runtime batch size reduced from 256 → 64, cutting peak ORT arena allocation during backfill by ~4×.
- Body-fetch chunk size reduced from 500 → 100 messages per SQLite batch.
tokio::task::yield_now()between every sub-batch keeps the UI thread responsive; an additional 20 ms sleep per chunk gives the OS room to reclaim freed pages.- Message body
Strings are freed immediately after each embed call (HashMap::remove) instead of being held until end-of-batch. - HNSW graph build no longer clones the full
VectorIndex.databuffer (~900 MB);Vec<E5Point>is built directly from a single short-lived read guard. - HNSW serialisation streams through a
BufWriter→ atomic rename instead of materialising the whole graph as aVec<u8>in RAM (~1 GB peak). vector_cache.binreads stream through aBufReader(halving the ~1.8 GB transient peak); writes stream through aBufWriter→ atomic rename (eliminating the ~900 MBencode_cachebuffer).- Settings → AI shows an amber “Low-memory mode active (system has
≤ 16 GB RAM)” badge. The badge appears correctly even when Settings is
opened after the embedding model has already finished loading, via a new
is_low_memory_modebackend command. - Override with
SYNAPSE_FORCE_LOW_MEMORY=1insrc-tauri/.envto test the low-memory paths on any machine.
[0.2.7] - 2026-05-11
Added
- Mail search (DSL + hybrid retrieval): Gmail-style query operators
(
from:,to:,subject:,before:/after:,older_than:/newer_than:,is:unread,has:attachment,in:,account:, phrase quotes, exclusions,OR, and more) compiled to SQLite FTS5 and metadata filters, with optional on-device semantic search fused via weighted RRF, intent classification, thread-level aggregation, recency shaping, and a lightweight feature reranker. - Search indexes and migrations: rebuilt FTS5 with extra columns and prefix
indexing, trigram fallback (
messages_fts_tri), vector chunks for embeddings, attachments FTS, saved searches, personalization signal storage, and B-tree indexes to speed address filters and contact typeahead. - Automated search tests: integration tests with deterministic fixtures and
an optional YAML-driven quality eval harness (
search_eval), plus a release gate that runsnpm run testbefore production builds inpublish-release.ps1. - Documentation:
docs/search.mddescribing behavior and operators. - Windows Start Menu shortcut: the installer now asks whether to add
Synapse to the Windows Start Menu (default: yes). Silent installs (
/S) automatically create the shortcut. The shortcut is removed cleanly on uninstall regardless of the choice made at install time. - Settings → AI: The embeddings section shows which compute device is in
use for vector embeddings (NPU, GPU, or CPU), plus the adapter
name when DirectML is active. The backend reports this on
embeddings://init-statusand viaget_embeddings_ep_infoso the label stays correct if Settings opens after the model is already loaded. - Adaptive GPU preference: On machines without a battery (desktops),
the DirectML adapter list now uses
DXGI_GPU_PREFERENCE_HIGH_PERFORMANCEso the discrete GPU is preferred over the integrated one, giving faster backfill throughput. On laptops and tablets (battery present) the previousDXGI_GPU_PREFERENCE_MINIMUM_POWERordering is preserved to save battery. NPUs remain first in both cases.
Changed
- Account avatars: When an account has a sidebar label and no custom uploaded avatar, the initials fallback (shown if Gravatar, Libravatar, and brand logo do not resolve) is derived from that label instead of the display name or email local part. Tooltip and accessible name for the avatar prefer the label as well.
- Search UI: richer search bar (chips, filters, improved typing behavior), search-mode integration in list/thread views, and a compact loading spinner instead of wide “Searching…” text.
- AI / infra: embedding pipeline and chunking updates; attachment indexing helpers; reminder and vault-related robustness aligned with new fields and SQLCipher open ordering.
- Split-bottom layout: The horizontal resize strip between the mail list and reader pane uses a subtle border-colored fill at rest so the divider is easier to see; hover feedback while resizing is unchanged.
Fixed
- Settings + sidebar: Clicking a folder (including Conversations and reminder folders) while Settings is open now returns to the mail view and selects that folder. Sidebar row buttons no longer show a clipped focus ring (light blue above/below the row) after that transition.
- Search correctness: stricter application of structural filters (e.g. unread) across folder and conversation search paths; trigram fallback no longer reintroduces rows excluded by FTS negation.
- Intent and ranking: person vs filter-only classification order and recency weights tuned so newer results can surface appropriately where intended.
- Misc: calendar invite UTC formatting for tests; streaming thinking-strip
test alignment; Italian bulk-email marker coverage; secrets store
Debugand key validation ordering for clearer wrong-passphrase errors. - Mail list: Conversation row context menu vertical position now accounts for reminder-related items (dismiss, ignore rules) before opening, so the menu stays above the Windows taskbar when right-clicking near the bottom of the screen.
[0.2.6] - 2026-05-08
Added
.emlattachment viewer: Right-click an.emlattachment and choose Open in Synapse to open it in a dedicated viewer window so the main inbox stays unchanged.
Fixed
- Gmail reply threading: Replies sent via the Gmail API now include the
stored native
threadIdso messages stay in the correct conversation instead of starting a new thread.
[0.2.5] - 2026-05-06
Changed
- Forward in folder views: Forward is no longer limited to the unified conversation layout; it is available in ordinary mail folders as well. It remains hidden in Drafts, where forwarding is not meaningful.
Fixed
-
Local AI semantic search on DirectML: ONNX Runtime only allows one DirectML execution provider per process; registering multiple adapters (NPU plus GPU) could abort embedding initialization with “Provider DmlExecutionProvider has already been registered”. The embedding session now registers a single best adapter (NPU preferred over GPU, then CPU fallback and retry if the slot is already taken).
-
Application exit: fixed a bug where using the window close control did not fully shut down Synapse.
-
FastEmbed cache directory: running the portable
.exefrom the Desktop (or another ad-hoc working directory) could create a.fastembed_cachefolder beside the executable. The cache is now written to the proper application data location. -
Permanent delete from Trash: deleting a message that is already in Trash now completes successfully.
-
Message body and context menus: right-click menus no longer stay open when you click inside the email body; the menu closes as expected.
[0.2.4] - 2026-05-06
Added
- Skip trash confirmation: new toggle in Settings → General to bypass the “Move to Trash?” confirmation popup when deleting emails or conversations. The permanent-delete confirmation (shown when deleting items already in Trash) is always preserved regardless of this setting.
Fixed
-
Search mode buttons in split-bottom layout: the FTS / AI / Hybrid toggle buttons were sliding under the fixed window-control overlay in split-bottom mode (where the search bar spans the full window width). Added right-side clearance padding so the buttons are always fully visible and clickable.
-
Delete confirmation dialog: replaced the native browser
confirm()popup (which displayed an ugly “Localhost says” header in the WebView) with a custom in-app dialog that matches the application’s visual style. Trash-move dialogs use the primary button color; permanent-delete dialogs use a destructive red button to signal the action is irreversible. -
Message list relative dates: the mail list showed only the time of day for messages received on the previous calendar day when fewer than 24 hours had elapsed (e.g. 18:00 yesterday vs. noon today). It now compares calendar midnights so Yesterday and weekday labels match the full date shown in the reader.
-
Windows “Open with” for
.emlfiles: choosing Synapse from the context menu launched the app but did not open the standalone EML viewer. Windows passes paths likeC:\…\file.eml; the URL parser treated the drive letter as a bogus scheme and the path was skipped. Paths with a drive letter are now recognised before URL parsing (cold start and when Synapse is already running).
[0.2.0] - 2026-05-05
Fixed
- Production OAuth flow: Google (and Microsoft) OAuth credentials are now
baked into the release binary at compile time instead of being read from a
.envfile at runtime. On a freshly installed machine no.envfile exists, so previously the app fell back to the placeholder value and blocked the user with “OAuth client ID not configured” the first time they tried to add a Google account. - Onboarding → Accounts tab: The “Add Your First Account” button on the welcome screen now opens Settings on the Accounts tab directly instead of landing on the General tab.
- Onboarding button draggability: The “Add Your First Account” button was accidentally treated as a drag handle for the frameless window; it now responds to clicks reliably.
- Auto-update manifest parsing: The
latest.jsonrelease manifest was written with a UTF-8 BOM by PowerShell 5.x, which causedserde_jsoninside the Tauri updater to reject it with “Could not fetch a valid release JSON from the remote”. All files produced by the publish script now use UTF-8 without BOM consistently.
[0.1.0] - 2026-05-04
Added
Email & Accounts
- Google (Gmail REST API + IMAP IDLE) and generic IMAP/SMTP account support
- PEC (Italian certified email) support with auto-configuration for the most popular Italian PEC providers
- Multi-account unified inbox with per-account unread badges
- Conversation (thread) view with expand/collapse and newest-first option
- Real-time push sync via IMAP IDLE; offline-first SQLite cache
- Two-way read/unread status sync
- Mark as spam / not spam, star emails
- Bulk actions via multi-select
- Move emails between folders
- Forward emails including attachments
- EML file viewer with system file-association registration
Compose
- Multi-window compose with rich text formatting
- Inline image paste and drag-and-drop
- Attachment drag-and-drop with context menu
- Smart recipient autocomplete with address-book integration
- Collapsible CC / BCC fields
- Spellcheck with multi-language dictionary support (including Italian)
Search
- Hybrid full-text + semantic search powered by on-device vector embeddings
- Multilingual embedding support
AI (on-device, fully private)
- Local LLM integration (Qwen, GPU-accelerated via Vulkan: no cloud required)
- AI reply suggestions with grammar-constrained structured output
- Stop button and configurable writing-language support
- Asana plugin: create Asana tasks directly from an email thread
- ClickUp plugin: create ClickUp tasks directly from an email thread
- Calendar plugin: accept, tentatively accept, or decline ICS calendar invites via Google Calendar API, directly from the email banner
- Reminders plugin: surfaces follow-up reminders for emails that need a reply or action, with per-thread dismiss support
UI / UX
- Custom frameless window with native-feel drag region and window controls
- Resizable multi-column layout with persisted column widths
- Collapsible and resizable sidebar with per-account folder visibility toggle
- Full theming system: Light, Dark, Aurora Violet
- Aurora gradient mesh avatars; branded provider logos as fallback
- Ctrl + Scroll wheel zoom in the mail reader
- Scroll-to-top button in mail list
- Unread-on-top ordering (configurable)
- Mark as read on open (configurable)
- Right-click context menus throughout (mail list, conversations, attachments, address fields, address book)
- Keyboard shortcut system with configurable presets
Infrastructure
- Encrypted credential vault (SQLCipher): all OAuth tokens and passwords are stored encrypted at rest; vault is unlocked with a master password on launch
- Auto-updater with silent background checks and passive install on Windows
- Address book with contact management and right-click actions