- Expose the sync limit in the config panel: "Fetch all mail" checkbox (sync_limit=0) + a max-per-folder number input.
- Config fields are editable while the bridge runs; Save persists anytime, a Restart button (stop+start) applies changes.
- Fix the in-process restart: stop() was fire-and-forget and left IMAP/SMTP tasks holding their ports, so the next start failed to bind and aborted the new syncer before it listed folders (empty store). stop() now aborts all three tasks and awaits full teardown.
Previously sync_limit=0 still loaded a single 1000-entry page, so the
mailbox was effectively capped. Use the SDK's paginated load_all to walk
the entire MailSetEntry list when the limit is 0, keeping a single capped
load_range for a finite limit.
Live-tested: with sync_limit=0, INBOX loads all 19288 mails (was capped
at 500) with stable UIDs 1..19288.
UIDs were assigned from an in-memory counter that reset to 1 on every
bridge restart, so the UID<->mail mapping changed each run and IMAP
clients (Thunderbird) re-downloaded the whole mailbox on reconnect.
Persist a per-folder monotonic UID in the local store (schema v3):
- mails gain a `uid` column; sync_state gains a `next_uid` counter that
only ever advances (UIDs are never reused).
- The syncer keeps each mail's existing UID and allocates new ones for
new mail (oldest-first, so newer mail gets higher UIDs).
- refresh_mails uses the persisted UID instead of allocating; messages
are ordered by UID. UIDVALIDITY stays constant.
Migration v2->v3 drops the cache tables and re-syncs once (encrypted
.eml files survive). Live-tested: UID<->mail mapping is identical before
and after a restart (range 1..500 unchanged), so clients fetch only the
delta instead of re-downloading.
Implement MOVE / UID MOVE (RFC 6851): resolve the target by IMAP path
(UTF-7 decoded), call the SDK move_mails for the selected messages, then
expunge them from the source view. Advertise the MOVE capability.
COPY is rejected with NO — Tuta folders are exclusive, so duplication
isn't supported; clients use MOVE instead.
FolderInfo gains the folder's list id so the target MailSet IdTuple can
be reconstructed. Pulls in the SDK move_mails (tuta-repo submodule bump).
Live-tested: a mail moved from one custom folder to a nested UTF-7 folder
lands in the target and leaves the source server-side.
The syncer ran one loop: phase 1 (folder list + mail-id lists) then
phase 2 (body prefetch). On a large mailbox the cold prefetch pass takes
many minutes, so the next folder refresh was stuck behind it and new
folders/mail only showed up after a restart.
Split into two independent loops sharing the store: a fast list_sync_loop
(folder list + mail ids, ~every SYNC_INTERVAL) and a slow prefetch_loop
(bodies, background). Folder and new-mail refresh no longer wait on
prefetch.
Live-tested: a folder created while the bridge runs appears over IMAP in
~18s, no restart.
Add an imap::utf7 module (RFC 3501 §5.1.3) and apply it at the protocol
boundary: encode mailbox names in LIST, decode them in SELECT/STATUS.
Folder names internally stay UTF-8; only the IMAP wire form is UTF-7.
Also fix STATUS argument parsing to handle quoted mailbox names with
spaces (it split on the first space, truncating names like
"Not Important" or nested paths).
Live-tested: "Café" lists as "Caf&AOk-", a nested child lists as
"Caf&AOk-/Test dossier avec espace", and SELECT/STATUS resolve both.
Key the syncer, local store and IMAP server by Tuta MailSet folder id
instead of the system folder kind, so custom (and nested) folders are
first-class.
- tuta.rs: add FolderInfo; MailBackend.list_folders enumerates system +
custom folders via the SDK FolderSystem tree, building IMAP paths and
RFC 6154 special-use flags; load mails by the folder's entries list.
- store.rs: folder_kind INTEGER -> folder_id TEXT, schema v2 with a
migration that drops the cache tables and re-syncs (encrypted .eml
files survive).
- sync.rs: MailStore keyed by folder id; the syncer enumerates the live
folder list each cycle.
- imap/session.rs: dynamic LIST/SELECT/STATUS driven by the folder list;
drop the hardcoded six-folder mapping.
Pulls in the SDK FolderSystem tree (tuta-repo submodule bump).
Live-tested: 9 custom folders listed and selectable over IMAP, with
headers and decrypted bodies. Not yet covered: nested-folder paths,
modified UTF-7 for non-ASCII names, labels, and IMAP MOVE/COPY.
Split the bridge into a tutabridge-core crate, a Tauri v2 desktop app
(src-tauri) and a React/TS UI (ui), keeping the CLI entrypoint at the
workspace root.
Add encrypted local storage (SQLCipher metadata index + encrypted .eml
files) so mail persists across launches and only the delta is fetched.
Wire the bridge to the Tuta Rust SDK via the tuta-repo submodule
(batch loading, MailDetailsBlob reading, interactive 2FA login).
Implement SMTP sending: build the draft and send it through Tuta's
DraftService/SendDraftService, mirroring the web client (body in
compressedBodyText, non-empty sender/recipient names, populated
SendDraftParameters). Add unit tests for the draft/send payload building.
- Add tuta-repo as git submodule pointing to spartanz51/tutanota
branch feat/rust-sdk-blob-read (pending upstream PR)
- Remove inline load_mail_details_blob hack, call through
mail_facade().load_mail_details_blob() instead
- Remove /tuta-repo from .gitignore since it's now a submodule
Local bridge that exposes Tuta encrypted email via standard
IMAP/SMTP protocols for use with Thunderbird and other clients.
Features:
- IMAP server with TLS (STARTTLS self-signed cert)
- SMTP server for sending mail via Tuta
- Session persistence via macOS Keychain
- Mail body decryption including LZ4-compressed blobs
- Blob storage access (BlobAccessTokenService + blob server)
- RFC 2822 message formatting
- Interactive first-run configuration