Skip to content

Releases: lyquyduong/skimmail

v1.18.0

Choose a tag to compare

@lyquyduong lyquyduong released this 18 Sep 09:59

Added

How much of this has actually been watched working. Gmail was verified
end to end on 2026-09-17, and a personal Outlook account on 2026-09-18 —
both with a real app registration, folders listed and mail synced. Two gaps
remain, and they are stated rather than glossed: a Microsoft 365 work or
school
account has never been tried (different registration, an
administrator who can block the app), and the Microsoft account has only been
watched for a few hours rather than over days. Its first renewal HAS been
watched and it came through cleanly — which matters, because Microsoft
replaces the stored sign-in every time it renews and a mistake there would
end the connection for good. If you connect a Microsoft mailbox, please
report what happened either way.

  • Connect a Gmail or Outlook mailbox without a public web address. Until
    now, signing in with Google or Microsoft needed SkimMail to be reachable from
    the internet, because the provider sends your browser back to it. On a home
    or office network there was no way to finish. There is now a second route:
    approve the sign-in as usual, then copy the address your browser lands on —
    it will show an error page, which is expected — and paste it back into
    SkimMail. Add account ▸ Google ▸ No public address?
  • See when a mailbox's sign-in expires, and renew it yourself. An OAuth
    account's details now show how long its access token is good for, with a
    Refresh button. The countdown is information, not a warning: these lapse
    about hourly and renew themselves.
  • A mailbox whose sign-in has actually failed now says so. A red Needs
    reconnect
    badge appears on the first failed sign-in rather than after
    SkimMail has given up retrying — so the warning arrives while your mail is
    still arriving, not after it stopped. If you have the alert channel for
    expiring sign-ins switched on, it fires at the same moment.
  • Optional: sign-in validity on hover. Appearance ▸ Show token validity on
    hover
    adds how long a Gmail or Outlook sign-in is good for to the sidebar
    hover card. Off by default, because reading it costs a lookup each time
    you hover and almost never needs your attention — the countdown is
    information, and the red badge is the part that asks you to act.

Fixed

  • "Show images" now actually shows them. Pressing it changed nothing: the
    images stayed broken however many times you tried, while opening the very
    same image address in a browser tab displayed it perfectly. The reading pane
    frames each message in a fully locked-down frame, and a side effect of that
    lockdown was that the browser stopped attaching your session to anything the
    message asked for — so every image came back "not signed in" and the server
    never even got as far as fetching one. The frame is still locked down in
    every way that matters: nothing in a message can run code, submit a form,
    open a window or navigate anywhere.

  • A remote image that fails to load now says why in the server log. It
    still shows as a broken image in the reading pane — that is deliberate, since
    a blank placeholder would be indistinguishable from a tracking pixel that
    loaded successfully — but until now nothing anywhere recorded the reason.
    Eight different causes, from a blocked address to a dead CDN to a file that
    was not really an image, all produced the same empty response and not one
    line in the log. Each now names itself. The image's host is recorded; its
    full address is not, because image links in mail are usually unique to the
    recipient and the log can be downloaded.

  • The pasting flow now hands you the sign-in link instead of only opening a
    tab.
    It used to offer a single "open in a new tab" link, which assumes the
    browser showing SkimMail is the browser you are signed in to Google or
    Microsoft with. For a headless box on a home network — the deployment this
    flow exists for — that assumption is usually wrong. The URL is now shown with
    a Copy button so you can open it wherever you are actually signed in,
    including on a phone; the new-tab link is still there for when this browser is
    the right one. Both copy buttons in that panel also confirm they copied,
    rather than doing nothing visible when the browser refuses clipboard access —
    which it does on a plain-HTTP address, exactly where this flow runs.

  • A failed Gmail or Outlook sign-in used to return you to an unchanged
    screen.
    The server worked out precisely why a sign-in had failed — you
    pressed Cancel, the sign-in expired, the provider refused the code, your
    account limit was reached, and four more — and sent every one of those
    answers back to the app in the address bar, where nothing read them. You saw
    no account and no message. Seven now appear as their own sentence; anything
    else, including a code invented by the provider, is shown with that code
    printed unchanged so you can search for it.

  • Gmail and Outlook sign-in asked for the wrong permissions. SkimMail
    requested permissions for the providers' web APIs, which their mail servers
    do not accept — so the sign-in could complete and the mailbox still never
    sync. It now asks for the mail permissions Google and Microsoft document for
    this purpose. Anyone who had connected a Gmail or Outlook account must
    reconnect it once
    ; existing sign-ins were granted under the old
    permissions and cannot be upgraded in place.

  • Mailboxes added by signing in ignored your "Default sync depth". The
    same setting that 1.17.0 fixed for IMAP accounts was still being skipped for
    Gmail and Outlook, which always started at 30 days. Reconnecting an existing
    mailbox still leaves its own setting alone.

  • Two things refreshing one mailbox's sign-in at the same moment could break
    it permanently.
    On Microsoft accounts, whichever finished last could store
    a sign-in the provider had already invalidated, after which only reconnecting
    would recover it.

  • Signing in with Google now actually connects the mailbox. Credentials
    entered in the browser — which is where SkimMail tells you to put them — never
    reached the background sync process, so the sign-in completed, the account
    appeared, and then every sync failed with "provider not configured". This is
    the most likely reason nobody has had Gmail sign-in working before now. It
    only ever worked if the credentials came from environment variables.

  • Saving one instance setting no longer erases the others. Saving an OAuth
    client ID silently cleared your public URL, base path and trusted origins, and
    turned off "trust proxy headers" — settings that decide where sign-ins return
    to, which origins may talk to your server, and whether session cookies are
    marked secure. The same happened with skimmail config set. It was invisible
    on instances where those values come from environment variables, and it was
    waiting for anyone who configured SkimMail the recommended way, in the
    browser.

  • A save that succeeded no longer reports a failure. Storing OAuth
    credentials on a server whose public URL comes from the environment answered
    "Not saved: BASE_URL is set in the environment" — true of a field you had not
    touched, printed beside a save that had worked.

  • Reconnecting a stopped mailbox now actually restarts it. If SkimMail had
    given up on a Gmail or Outlook account after repeated failures, signing in
    again stored a fresh, working sign-in and then did nothing at all — no error,
    no hint, and no way forward from the screen. It now resumes the account the
    same way re-entering an IMAP password always has. This matters on this upgrade
    in particular: the permission change above means every OAuth mailbox must be
    reconnected once, and many of them will already have been stopped.

  • The sidebar hover card and Settings no longer disagree about the same
    account.
    One called a dead sign-in "Sync failed" in amber while the other
    called it "Needs reconnect" in red — two different verdicts, suggesting two
    different actions, for one mailbox at one moment.

  • Settings ▸ About reported the wrong database. An instance moved onto
    PostgreSQL by the first-run wizard kept showing SQLite.

  • Several screens were shown to people who could not load them. The Logs
    tab offered an operator controls that only an owner may use, and the resource
    monitor could be left switched on by someone who no longer has permission to
    read it.

Security

  • On a multi-user instance, an operator could act on a mailbox belonging to
    somebody else. This was present in 1.10.0 through 1.17.0 — see Upgrading
    below.
    "Sync now" took the account number straight from the address and
    never checked who owned it, so naming another person's account made the
    server open their mailbox with their stored credentials, report back how
    much mail it found, and — for a Gmail or Outlook account — renew their
    sign-in. The same route could also silently drive another person's failure
    counter until the automatic cut-off switched their mailbox off, and write a
    chosen error message onto their Accounts screen. Both now refuse an account
    you do not own, and refuse it the same way they refuse one that does not
    exist. A single-user instance was never exposed, because there is no other
    account to name.

  • A message could no longer turn one click into a request to your own
    server.
    Making the message frame same-origin, so that images could load at
    all, also meant an ordinary-looking link in a stranger's mail could point back
    at SkimMail's own address and be followed while signed in. Links in mail that
    point anywhere other than out to the web are now inert. Anchors to a place
    within the same message still work, and so does mailto:.

  • Image addresses from your mail no longer reach the log. When an image
    could not be fetched, the recorded reason carried the ful...

Read more

v1.17.0

Choose a tag to compare

@lyquyduong lyquyduong released this 17 Sep 01:16

Fixed

  • The unsubscribe shortlist now counts a sender once, however they sign
    themselves.
    Senders were identified by the whole "From" line, so the same
    newsletter arriving as Weekly Digest, then Shop Weekly, then Shop Weekly
    Deals
    counted as three separate senders — each with too few messages to
    reach the shortlist. On a real mailbox one sender had come apart into
    nineteen. They are now identified by their address, which is what does not
    change, and senders that were hidden this way appear.
  • Muting a sender now removes them from the shortlist. It never did. The
    shortlist compared your muted list against the full "From" line rather than
    the address, so the two could never match and a sender you had already
    silenced kept being offered. The unsubscribe link SkimMail had already
    discovered for a sender was being missed the same way, and is now used.
  • A sender's image setting applies again. "Always load images from this
    sender" and "never" were saved correctly and then ignored for every sender
    whose mail carries a display name — which is nearly all of them — so those
    messages fell back to asking you each time. Remote images are how a sender
    learns you opened their mail, so a "never" that quietly did nothing was the
    wrong way round.
  • "Default sync depth" now does what it says. The field in
    Settings ▸ Sync — and the SYNC_DEPTH_DAYS environment variable behind
    it — were saved, shown back to you, and read by nothing: every account you
    added synced 30 days regardless. New accounts now start from whatever you
    set there, and typing a number on the Add Account form still overrides it
    for that one account. Accounts you already have keep their own setting; a
    window you chose for one mailbox is not rewritten because the default moved.
  • The first-run screen no longer gives Docker users a command that fails.
    It suggested docker logs skimmail, which does not work with the compose
    file SkimMail ships — the container is not called skimmail. It now shows
    docker compose logs skimmail. The binary hint was also incomplete: it left
    out the data directory, without which the command refuses to run. Both
    matter more than most text in the app, because you read them when you cannot
    sign in yet.

Upgrading

  • This release adds a database column and fills it for mail you already have.
    It runs once, during the first start after upgrading, and a large mailbox may
    make that start take a few seconds longer than usual. Nothing is re-fetched
    from your mail server.

v1.16.2

Choose a tag to compare

@lyquyduong lyquyduong released this 16 Sep 07:29

Fixed

  • After unlocking, you get your inbox back — not the "Your first mailbox"
    screen.
    If the screen lock came on and the page reloaded while it was up —
    you pressed F5, your browser put the tab to sleep, your laptop woke up, or you
    reopened the app — SkimMail asked for your mailbox list, was refused because
    the session was locked, and wrote that down as "this person has no mailboxes".
    Typing your passcode then revealed the setup screen instead of your mail, and
    only a page reload fixed it. It now knows the difference between being
    refused and finding nothing, and the mailbox list is fetched again the moment
    you unlock.
  • The rest of the app stops reporting failed requests as empty ones. The
    same mistake was in a dozen other places: a failed message load showed "Inbox
    Zero", a failed folder list made your folders disappear, the security screen
    could tell you that you had no active sessions while you were sitting in one,
    and the setup hub could claim no mailbox was connected on an instance that had
    several. Each of these now says it could not load, and offers to try again.
  • Only being signed out signs you out. A server error while the app was
    starting used to show the login screen, which reads as "your session ended"
    when it had not.
  • A locked session now locks every tab. The lock lives on your session, but
    other open tabs kept showing your mail until you touched them. They now draw
    the lock screen within a few seconds — and, because the lock is the server's
    decision and not the browser's, only the server can lift it again.

v1.16.1

Choose a tag to compare

@lyquyduong lyquyduong released this 15 Sep 12:55

Fixed

  • Mail you cleared somewhere else no longer lingers here. Until now a sync
    only ever added: delete a message from your phone or from webmail and its row
    stayed in SkimMail for good — still listed in the inbox, still counted as
    unread, still folded into every bundle total. Each sync now also asks the
    server which messages your inbox still holds and drops the ones that are
    gone, along with their cached copy and their search-index entry. Nothing is
    deleted on the mail server; SkimMail is only catching up with a deletion you
    already made. If the server cannot be asked, nothing is removed — a failed
    lookup never throws mail away. Mail deleted before you upgrade is cleared on
    the first sync afterwards.

v1.16.0

Choose a tag to compare

@lyquyduong lyquyduong released this 15 Sep 06:45

Added

  • Tag mail yourself. Tags have existed since 1.4.1, but only a rule could
    apply one — there was no way to label the message you were looking at. Now
    there is: press T while reading, use ⌘K, or select several messages and
    tag them all at once. The tag shows on the message itself, with an × to take
    it off.
  • Take a tag off again, and fix one you regret. Until now a tag could only
    be added. A rule with a typo in it branded every message it matched, for
    good — deleting the rule left the label behind. Settings ▸ Rules ▸ Tags
    lists every tag in use with its message count, and lets you rename one,
    merge two by renaming one onto the other, or remove one from every message
    it is on. The mail itself is never touched.
  • Apply a new tagging rule to the mail you already have. Rules only ever
    saw mail as it arrived, so saving "tag everything from billing@ as finance"
    did nothing to the invoices already in your inbox — which is why tagging
    looked broken. A rule with a tag action now has a Backfill button: it
    counts what it would tag before it changes anything, and you decide. It
    never overwrites a tag that is already there, and it never sends a
    notification for old mail.
  • tag: in the search box. Type tag:finance to filter, or
    tag:finance invoice to search within a tag. Quote it if the tag has a
    space: tag:"to read". The tag chip now stays visible while you search, so
    a filter can no longer narrow your results invisibly.
  • A switch to turn background prefetch off. SkimMail fetches new message
    bodies ahead of time so they open instantly. 1.15.0 disclosed that this had
    been marking mail read on your own server and fixed it, but left you no way
    to say "just do not do this". Settings ▸ Security, beside the cache
    controls, because it is the same question: how much of your mail sits on
    this disk, and how it got there. It stays on by default, including after
    an upgrade — the reason it was visible is fixed, and quietly changing a
    performance default under people is its own kind of surprise. With it off,
    opening a message still fetches the body on demand.

Fixed

  • deploy/docker-compose.yml gave a command that cannot work. It told you
    to read the one-time claim code with docker logs skimmail, but under
    docker compose up -d the container is not named that, so the command
    answered "No such container" — on a fresh install, at the one step that gates
    your very first sign-in. It now says docker compose logs skimmail.

v1.15.0

Choose a tag to compare

@lyquyduong lyquyduong released this 14 Sep 11:53

Fixed

  • SkimMail was marking your mail as read on your own mail server. When new
    mail arrived, SkimMail quietly fetched the message body in the background so
    it would open instantly later — and the way it asked for that body also told
    your mail server "this has been read". The next sync brought that state back,
    so mail you had never opened appeared as read everywhere: here, in webmail,
    on your phone. Downloading an attachment and using one-click unsubscribe did
    the same. It has been happening since background prefetch arrived in 1.11.0.
    SkimMail now reads without touching the flag. Mail already marked read this
    way cannot be told apart from mail you genuinely read, so nothing can undo
    it
    — but it stops here.

Added

  • Unsubscribe shortlist — the senders filling your inbox that you almost
    never open, each with unsubscribe and mute offered side by side, and a note
    explaining why they are not the same choice: unsubscribing sends a request to
    the sender and confirms your address is live, muting is local and tells them
    nothing. Open it from the command palette (⌘K, "unsubscribe") or from the chip
    above the Newsletters filter. No AI key, no vendor, no outbound request to
    build the list — it is your own mail, counted.

Changed

  • The unsubscribe shortlist finds far more of what you actually want to cut.
    It used to look only at mail filed under "Newsletter", and that label is
    guessed from English keywords in the sender and subject — so a sender whose
    mail landed partly in Newsletter and partly in Primary was mostly invisible,
    and a newsletter in Vietnamese or Chinese was invisible entirely. It now uses
    the unsubscribe header that bulk senders attach themselves. That is also why
    account-security notices never appear on the list: they do not carry one.
  • An empty shortlist now tells you why. Nothing synced yet, nothing checked
    for the signal yet, no bulk mail at all, or bulk mail that simply is not worth
    cutting — four different answers. It used to show the same congratulation for
    all of them, and for a failed request too.

Upgrade note

The unsubscribe signal is recorded while mail syncs, so immediately after
upgrading the shortlist will say none of your mail has been checked yet. Sync
once and it fills in. Mail older than an account's sync window is never re-read,
so the list covers recent mail.

v1.14.0

Choose a tag to compare

@lyquyduong lyquyduong released this 14 Sep 09:14

Security

  • Two more places let one account reach another's mailbox on a multi-user
    instance.
    Server-side search and the "Test connection" button on an
    account both took an account id straight from the request and used it
    directly — either could make the server open a DIFFERENT account's mailbox
    with that account's own saved password and network route, and run a search
    or a connection test against it. Fixed; single-user instances were never
    affected because there is nobody else to reach.

Fixed

  • An upgrade to 1.13.0 could leave the server unable to start at all, and
    every restart would fail the same way.
    The one-time migration that runs
    on upgrade could collide with itself if a mailbox had ever had a sender
    both unsubscribed AND muted — an ordinary combination. If your upgrade to
    1.13.0 got stuck like this, upgrading to 1.14.0 fixes it — see the Upgrade
    note below.
  • Settings ▸ Security ▸ Muted and VIP senders showed raw setting names
    instead of real text.
  • An empty Starred view no longer shows the "Inbox zero" message with a
    Sync button that could not have added anything to it; it now says there is
    nothing starred yet.
  • The Storage screen's database-migration guide no longer tells you a
    rebuild is required to switch to Postgres or MySQL. Every released build
    (tarball, .deb, apt, container) has shipped with those drivers built in
    since 1.4.3 — the guide just kept saying otherwise.

Added

  • Settings ▸ Storage is now visible. Switch the blob storage between the
    local filesystem and S3-compatible storage with a real connection test and
    a migration that resumes if interrupted (your existing files are left in
    place, so you can switch back); see which database driver is active and
    the exact steps to move to Postgres or MySQL.

Upgrade note

If your upgrade to 1.13.0 left the server stuck and unable to start, this
release is the fix.
Upgrade to 1.14.0 and restart — nothing else to do; the
migration that failed has been corrected in place, so the stuck instance
completes it instead of retrying and failing forever. Instances that upgraded
to 1.13.0 cleanly are unaffected either way.

v1.13.0

Choose a tag to compare

@lyquyduong lyquyduong released this 14 Sep 05:58

Security

  • Search no longer returns other people's mail. On a multi-user instance the
    search box returned matches from every account on the server, not just yours —
    subjects, senders, previews and AI summaries. Anyone who could sign in could
    read them. Fixed; single-user instances were never affected.
  • The AI "catch me up" digest had the same flaw, and it mattered more: it
    reads the opening lines of message bodies and sends them to your AI provider.
    It now only ever reads your own unread mail.
  • Archive, Move and Delete could act on someone else's message. A message id
    from another account was moved on the real mail server before the ownership
    check ran, and the caller was told it had worked.
  • AI summaries were readable across accounts through the result cache, which
    answered before the ownership check.
  • Accounts added by "Sign in with Google/Microsoft" were saved without an
    owner, so they belonged to nobody and disappeared from your account list. If
    two people connected the same mailbox, one overwrote the other's sign-in.
  • Muting a sender was instance-wide. One person muting a newsletter hid it
    from everyone; meanwhile the mutes and VIPs you set were saved where nothing
    could read them back, so Settings never showed them. Both are fixed together,
    and an upgrade adopts the mutes that were stranded — you should not need to
    set them again.

Added

  • Starred — a view in the sidebar listing everything you have starred, across
    all accounts. The star has been there since the first release with no way to
    see what you had marked.
  • Mute sender in bulk — select messages and silence their senders in one go.
    It names the senders and asks first, because it affects more than the messages
    you have selected.
  • Move to folder — the reading toolbar can now file a message into any folder
    of that account, with the same undo as Archive.
  • Message counts on the split-inbox filter (Primary, Newsletters, and so on).
  • Muted and VIP senders are now listed in Settings ▸ Security, and can be
    undone there. Until now muting was a one-way door.

Changed

  • Choosing Server search in the unified inbox now says it needs a single
    account and lets you pick one, instead of quietly returning cached results only.

Upgrade note

One migration runs on first start. It re-assigns muted and VIP senders that were
saved without an owner to the first user on the instance. On a single-user
install that is simply you. On a multi-user install the original owner was never
recorded and cannot be recovered, so review Settings ▸ Security if the list looks
unfamiliar.

v1.12.0

Choose a tag to compare

@lyquyduong lyquyduong released this 13 Sep 13:10

Fixed

  • On a multi-user instance, five things ignored who was asking. Anyone
    signed in could snooze or pin another person's message, delete another
    person's push-notification device, or read split-inbox counts computed over
    everyone's mail. Worst of the five: Reset groups deleted every user's
    groups, not just your own.
    All five are fixed and each now has a test that
    fails if the check is removed. Single-user instances — the default — were
    never affected, because there was nobody else to reach.
  • Tag chips are coloured again. They have been a single shade of violet
    since tags shipped, even though the 1.4.1 notes promised otherwise. Each tag
    now takes a colour from its position in your sorted tag list, so the same tag
    keeps the same colour.
  • Filtering to a tag with no matches no longer congratulates you. It said
    "Inbox zero" and offered a Sync button that could not have helped. It now
    says the filter matched nothing, and offers to clear it.
  • Tag names in Chinese, Japanese and Vietnamese are no longer cut short.
    The length limit counted bytes, so the same cap allowed about 40 Latin
    letters but only about 13 Chinese characters. It counts characters now.
  • Filtering by tag is faster on large mailboxes — that query had no index
    in any of the three databases.

Added

  • Screenshot-safe mode (Settings ▸ Appearance). Turn it on and every
    address, name, subject, preview line and attachment name is replaced with a
    realistic stand-in, so you can screenshot SkimMail — for the docs, or for a
    bug report — without publishing your mail. Message bodies are not rendered at
    all while it is on, because a body is whatever the sender wrote and there is
    no safe way to partly hide it. Display only: nothing is changed and nothing is
    sent. A badge stays on screen the whole time it is active.
  • Server keywords, off by default (Settings ▸ Appearance). If you tag mail
    in another client — Thunderbird, Apple Mail — SkimMail can now show those tags
    as read-only chips. It never writes them back. Gmail accounts will show
    nothing here
    : Gmail presents labels as folders and reports no keywords over
    IMAP, so this is for people running Dovecot, Cyrus, Mailcow and the like. Tags
    applied to mail older than your sync window will not appear until that mail is
    re-fetched.

v1.11.1

Choose a tag to compare

@lyquyduong lyquyduong released this 12 Sep 08:52

Fixed

  • BLOB_ENCRYPT=1 quietly turned encryption off. Cached message bodies are
    encrypted by default since 1.11.0, but the setting recognised only the exact
    word true — so 1, yes, on and TRUE all read as off, switching
    encryption off at the very moment an operator believed they had just
    confirmed it on. Only false, 0, no and off disable it now. If you set
    BLOB_ENCRYPT to anything else on 1.11.0, the bodies cached since then are
    not encrypted: nothing is rewritten in place, so they stay that way until
    they leave the cache. Lowering the size or age limit in Settings ▸ Security
    pushes them out; they come back encrypted.

Added

  • The remote-image controls finally have screens. In 1.11.0 the
    instance-wide off switch and the per-sender never choice existed only as
    API calls. Now the switch is in Settings ▸ Security, a Never from this
    sender
    button sits beside Always from this sender in the message itself,
    and a sender you have blocked can be un-blocked from the same banner — before
    this, the safer choice was the harder one to reach, and it had no way back.
  • The message body cache has a screen too — how much is cached, a size
    limit, a keep-for limit and the manual purge, in Settings ▸ Security. The
    panel shipped in 1.11.0 inside a tab this release does not display, so no
    install could open it.

Changed

  • The licence and the third-party notices now travel with what you install.
    The .deb puts them at /usr/share/doc/skimmail/, the container image at
    /licenses/. Tarballs still ship without them.
  • LICENSE names Solutions101 (S101) as the Licensor and now carries the
    notice the Business Source License requires — that it is not an Open Source
    licence. COMMERCIAL-TERMS.md no longer states a governing law: an earlier
    draft named Delaware and called it "the intended jurisdiction", which nobody
    had ever decided.
  • Release builds now stop if embedding the web app has left the source tree
    dirty, so published binaries are no longer stamped as built from modified
    sources.