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