Skim v1.0.12 β "Warm Sockets" π₯π§¦
The one where the email is already downloaded before you click it. Turns out the fastest way to fetch a message is to have fetched it earlier. Nobel committee, we're waiting. π
β‘ Faster
New mail arrives with its body attached. When a message lands, Skim quietly pulls the body onto the fetch connection right away β newest first, small ones only, three at a time. So tapping that notification is now a read from your own disk instead of a chat with the server. π¬
The connection wakes up when you do. Alt-tab back to Skim and it logs in and re-selects your inbox in the background, so your click pays for a FETCH and nothing else. Costs precisely zero when everything's already warm. π
Waking from sleep no longer confuses OAuth with itself. All connections now share one token cache, so two of them can't sprint for the same rotating Microsoft refresh token at the same time and both lose. πββοΈπββοΈ
Token requests got a timeout and a reused HTTPS client. Previously a black-holed auth endpoint could hang around for as long as Windows kept the socket alive. Now it gets 15 seconds and then it's out. β±οΈ
Discovering a dead connection dropped from ~22β40s to ~4β6s. One tiny NOOP up front instead of twenty seconds of staring at "Loadingβ¦". Bold, we know. π©Ί
π Fixed
Ordinary mail stopped getting the phishing banner. Receipts, calendar invites, ATS relays and basically every transactional platform on Earth were tripping sender checks that fired solo. They now need corroboration, exactly like the link checks always did. Actual attack shapes β forged display names, broken signatures, lookalike domains β still light up red. π¨
A rogue empty FETCH response can't clobber a real body anymore. Some servers weave unsolicited flag-only replies into the answer; one landing late was a fine way to be told "server returned no message body" over a perfectly healthy connection. π
"No message body" from a recycled connection now earns one honest retry on a fresh one, because that answer was usually the previous conversation still talking. π»
π§Ή Housekeeping
Slow fetches now log where the time actually went β db / creds / login / select / fetch β instead of one unattributable number that could mean anything. Numbers only; nothing from your mail. π
New tests pin down the prefetch budget, so nobody quietly turns "three small messages" into "your entire inbox over cellular". π§ͺ
Grab it here: https://github.com/nikserg/skim/releases/tag/v1.0.12


Replies
I'm curious, how much of a difference does the prefetching make for people with larger inboxes or slower internet connections?
Skim
@christian_onochieΒ
Inbox size: essentially no effect. Prefetch is per-arrival, not per-inbox. When new mail lands we pull at most 3 bodies, only ones the server reported as β€256 KB, with a 15s budget for the whole batch. A 200k-message mailbox and a 200-message one queue exactly the same work. What a large mailbox does slow down is the sync pass β but body fetches run on their own dedicated IMAP connection precisely so that opening a message never queues behind a sync.
Slow connections: that's where the win is biggest β and where the tradeoff lives. The bytes still cost what they cost; the point is that they move while nobody is waiting instead of under your click. Latency matters more than bandwidth, actually: the warm-up takes connect + TLS + login + SELECT off the click path, so on a high-RTT link you skip several round trips before the FETCH even starts.
The honest downside: a prefetch batch holds the fetch connection, so a click landing mid-batch queues behind it. That's exactly why the batch is capped at 15s and each message at 256 KB β on a slow link we deliberately skip attachment-bearing mail, where a short wait on open is expected anyway and where prefetching risks writing files to disk for a message nobody opens.
What I can't hand you is a benchmark across real-world connections. The one hard number I have is the adjacent fix: discovering a dead fetch socket used to cost ~22β40s and is now ~4β6s. The prefetch win I'd call structural rather than measured β it moves work off the click path rather than making the work itself faster. If you're on a slow link and it feels worse, that's a bug and I'd want the report.
The shared token cache sounds like a smart improvement. Did you moticed any unexpected performance gain after making that change beyond fixing the refresh token issue?
Skim
@kathrine_rolceΒ Honestly, no β it shipped a few hours ago and wasn't instrumented, so I have no number and I'd rather not invent one.
In principle it should save some redundant token refreshes: IDLE used to keep its own private cache while sync and fetch shared a second one, so three connections, two caches. But it also adds contention β IDLE now waits on the same mutex instead of refreshing on its own. That serialization is the fix, so "shared cache" isn't free.
The change in that batch that actually moved latency is the boring one next to it: token calls now reuse a single reqwest client with a 15s timeout instead of Client::new() per call. That path runs inside the body-fetch leash, so the handshake was time your click paid for.
the phishing banner fix is a good catch, false positives on receipts and calendar invites train people to ignore the warning entirely which defeats the whole point. one thing I'd want to understand though - now that sender checks need corroboration instead of firing solo, does that open a gap for an attack that's purely a spoofed sender with no bad link at all? that seems like exactly the case that used to trigger the sender check alone and now needs a second signal that a link-free phishing attempt wouldn't produce.