Releases: lyquyduong/skimmail
Release list
v1.18.0
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 withskimmail 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 doesmailto:. -
Image addresses from your mail no longer reach the log. When an image
could not be fetched, the recorded reason carried the ful...
v1.17.0
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 theSYNC_DEPTH_DAYSenvironment 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 suggesteddocker logs skimmail, which does not work with the compose
file SkimMail ships — the container is not calledskimmail. 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
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
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
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. Typetag:financeto filter, or
tag:finance invoiceto 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.ymlgave a command that cannot work. It told you
to read the one-time claim code withdocker logs skimmail, but under
docker compose up -dthe 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 saysdocker compose logs skimmail.
v1.15.0
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
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
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
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
Fixed
BLOB_ENCRYPT=1quietly turned encryption off. Cached message bodies are
encrypted by default since 1.11.0, but the setting recognised only the exact
wordtrue— so1,yes,onandTRUEall read as off, switching
encryption off at the very moment an operator believed they had just
confirmed it on. Onlyfalse,0,noandoffdisable it now. If you set
BLOB_ENCRYPTto 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.debputs them at/usr/share/doc/skimmail/, the container image at
/licenses/. Tarballs still ship without them. LICENSEnames 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.mdno 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.