Skip to content

Feature Accounts

SkimMail docs edited this page Sep 18, 2026 · 3 revisions

English · Tiếng Việt · 中文

Accounts

Since 0.1.0: multi-account IMAP, plus Gmail and Outlook by OAuth. This is the most basic thing SkimMail does — read several mailboxes from different providers in one unified inbox — and this page is its reference.

What this solves

A self-hoster's mail is rarely in one place: a personal Gmail, a work Outlook, a couple of IMAP mailboxes on a domain you run yourself. SkimMail connects to each independently, over each account's own network path if you give it one (see Connections), and keeps a local, read-first cache of headers (and optionally bodies — see Message body cache) so the unified inbox is fast without needing every provider to support the same protocol extensions.

Running this for more than one person? Read Security's per-user access control section — Sync now let an operator act on another user's account in every release from 1.10.0 through 1.17.0, fixed in 1.18.0.

Where it is

Settings ▸ Accounts. Listing accounts is available to every signed-in user (viewer and above); adding, editing, syncing, re-authenticating and testing a connection are operator actions; deleting an account is owner-only. See Users and roles for what each role means.

Adding an IMAP account

Host, port, security (tls / starttls / none), username (defaults to the email address if left blank) and password, plus two optional per-account overrides: an egress (which proxy, VPN or relay this account's traffic uses — see Connections) and a sync depth in days.

Adding one is a real connection test, not just a form save. SkimMail dials the server and lists its mailboxes before the account is ever written to the database — through the assigned egress if you picked one, so a misconfigured proxy is caught immediately rather than surfacing later as a silent sync failure. A failed validation refuses to save the account at all.

Adding a Gmail or Outlook account (OAuth)

Not sure which to pick? App password or OAuth? compares the two in plain terms. This page is the procedure once you have chosen.

There are two reasons to use OAuth rather than an app password, and only one of them is a preference:

  • Outlook.com and Hotmail have no alternative. Microsoft does not issue app passwords for personal accounts and has switched basic authentication off permanently. For those mailboxes OAuth is the only way in.
  • Gmail has an alternative, and it is simpler. An app password takes two clicks and no Cloud project. OAuth is worth the setup when a Workspace admin has blocked app passwords, or when you would rather not store a password that opens the whole Google account.

You bring the OAuth application

SkimMail ships no shared OAuth client of its own, and this is deliberate rather than unfinished. https://mail.google.com/ is a restricted scope: Google grants it only to applications that have passed an independent security assessment (CASA Tier 2), repeated annually. An application that qualifies is tied to whoever owns it, and a client secret published in an open-source repository would be revoked once and break every installation at the same moment. Thunderbird ships its own verified client and says in that same source file: "Don't copy these values for your own application — register one for yourself!" Roundcube and Nextcloud Mail take the same bring-your-own approach SkimMail does.

So: register an application — see Register the OAuth application for the console-by-console steps at Google and Microsoft — then paste its client id and secret into Settings ▸ About ▸ Setup health ▸ OAuth. Environment variables (GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET, MICROSOFT_CLIENT_ID/MICROSOFT_CLIENT_SECRET) still work and still win over the stored value, showing the field as locked and naming the variable to remove. Without both values for a provider, that provider's button is refused (oauth_not_configured) rather than shown broken.

The scopes SkimMail requests:

Provider Scopes
Google https://mail.google.com/, userinfo.email, openid
Microsoft https://outlook.office.com/IMAP.AccessAsUser.All, offline_access, openid, email, profile

Microsoft was connected for the first time on 2026-09-18 — recently, and not completely. A personal Outlook account was taken end to end: the sign-in completed, thirteen folders were listed and mail synced. Until that day nobody on this project had watched any of it work. Its first renewal has now been watched too, and it came through cleanly — which is the part worth reporting, because Microsoft replaces the stored sign-in every time it renews, and a mistake there ends the connection permanently rather than for an hour. What has NOT been watched: whether a Microsoft 365 work or school account behaves the same — that is a different registration, under an administrator who can block the app outright — and anything beyond the first few hours. Gmail was verified on 2026-09-17 and has been through a renewal. If you connect a Microsoft mailbox — successfully or not — please report what happened; these sentences only get shorter that way.

Both are the scopes their vendor documents for IMAP. SkimMail's sync engine speaks IMAP, so a scope that grants a REST API — Gmail's gmail.modify, or Microsoft Graph's Mail.ReadWrite — is not accepted by the mail server no matter how valid the token looks. Do not add a Microsoft Graph scope alongside the Outlook one: an access token carries a single audience, and mixing them either fails outright or, worse, silently issues a token for whichever resource was named first.

Two ways to finish signing in

Register both of these redirect URIs on the same application. They cost one line each in the provider's console, and the Setup health ▸ OAuth screen shows both with a copy button — typing them by hand is the commonest cause of redirect_uri_mismatch.

Redirect URI What it is for
<BASE_URL>/api/oauth/callback The ordinary flow. The provider sends the browser straight back to SkimMail. Needs a public HTTPS address.
http://localhost:8642 The manual flow. Nothing listens there.

The ordinary flow is what most installations use: click Continue with Google, approve, and you are returned to SkimMail. It requires BASE_URL to be set to an address the provider can reach.

The manual flow exists for everything else — a box on a home or office network with no public address, no domain and no TLS. Click No public address? Connect by pasting the sign-in result instead and SkimMail shows you the sign-in URL with a Copy button beside it.

Copy it and open it in whichever browser is signed in to the account — that is the point of showing the URL rather than only a link. A headless server is usually being administered from some other machine, and often the session signed in to Google or Microsoft is on a laptop or a phone rather than on the box running SkimMail. Paste it there. (If the browser you are reading this in is the right one, the "open it in a new tab here" link beside the URL saves you the round trip.)

Approve access, and that browser will land on http://localhost:8642/?code=…&state=… and fail to load. That failure is the design, not a fault: nothing is listening on that port. Copy the whole address out of the address bar and paste it back into SkimMail, which reads the code out of it and finishes the exchange.

The line you paste is protected by two independent locks, and it is worth knowing which is which. Your client secret — the provider will not exchange that code for a token without it, and it never leaves your server. PKCE — a one-time value your server keeps to itself and the provider checks. The secret alone already makes a copied line useless to whoever sees it; PKCE means that remains true even if a provider handles PKCE poorly. The sign-in expires after fifteen minutes; start again if you take longer.

Two traps that will otherwise catch you

The seven-day trap. A Google consent screen left in publishing status Testing issues refresh tokens that Google revokes after seven days, for any scope beyond name, email and profile — which includes the mail scope. The account then stops syncing and has to be reconnected, every week. Move the app to In production to stop it. The cost is an "unverified app" warning on the consent screen and a cap of 100 users, both of which are fine for a self-hosted instance.

The unverified-app warning is normal. https://mail.google.com/ is a restricted scope, so until the application passes verification the consent screen warns about it. That is expected and is not a sign that anything is misconfigured. It must not be "fixed" by requesting a narrower scope — a narrower scope is what stops IMAP working at all.

If you would rather not do any of this

A tunnel gives a LAN instance a real public HTTPS address, which turns it into the ordinary case: Cloudflare Tunnel and Tailscale Funnel both do this without opening a port. Then set BASE_URL to the address it gives you and use the ordinary flow. For Gmail specifically, an app password remains the shortest path and avoids all of the above.

Editing an account

Each field is patched independently — a request that omits a field leaves it unchanged, it is never reset to a default. What you can change after creation: display name, group membership (see Account groups), egress assignment, bind address, sync depth, and pausing or resuming that account's own sync (which also clears an auto-stop, the same as a successful re-auth does).

Re-authenticating

IMAP accounts have a Re-authenticate action that takes a new password, re-validates it the same way adding an account does, and — if the account's sync had been auto-stopped by repeated failures (see Sync and Sync Health) — resumes it immediately.

OAuth accounts have the same button, and it does the right thing. For a Gmail or Outlook account, Re-authenticate sends you back to the provider's consent screen rather than asking for a password. Coming back mints a fresh token and updates the account you already have — it is matched on provider and address, so you do not end up with a duplicate.

Knowing before it breaks

An OAuth account's detail panel shows when its access token expires and offers a Refresh button beside it. Access tokens last about an hour and are renewed automatically on the next connection, so a clock reading expired is ordinary and is drawn as plain text, not as a warning. Refresh is there for when you want to see the clock move now — or, more usefully, to find out whether the grant is still alive without waiting for a sync to fail.

One signal does mean act: a red Needs reconnect badge on the account. That appears when authentication itself has failed — the grant was revoked, expired, or rotated away — and only reconnecting will fix it. Retrying cannot. SkimMail raises it on the FIRST failed sign-in rather than after the retry limit, so the warning arrives while the mailbox is still working rather than after it has stopped.

If the alert channel for oauth_expiring is switched on (see Notifications), the same event is delivered there.

Do not delete the account to fix an expired token. Removing it throws away its settings, its egress assignment and its cached mail for no benefit; the button above is the whole fix.

Sync depth: the instance default, then the per-account override

Every account gets its own sync_depth_days, and the number it starts with is the instance default from Settings ▸ Sync ▸ Default sync depth (falling back to the SYNC_DEPTH_DAYS environment variable, and to 30 days if neither is set). Typing a number into the Add Account form overrides it for that account, because the most specific answer wins.

Before 1.17.0 that default was ignored. Account creation hardcoded 30 days, so an operator who chose 90 — or full history — got an account that synced 30 days anyway, with nothing in the logs and the Settings screen still showing the number they had picked. If you set that field on an older version and wondered why nothing changed, that is why.

Changing the default does not move accounts you already have. It applies to accounts created after the change. An existing mailbox keeps the number on its own row, which is deliberate: a window you chose for one account should not be rewritten because the instance default moved later. To change one, edit that account.

Deleting an account

Owner only, and it does one thing that matters beyond removing the row: the account's cached message bodies are condemned for cleanup inside the same transaction (see Message body cache), so a deleted mailbox does not go on counting against your storage meter forever. This removes SkimMail's local copy only — the mail itself is untouched on the provider, and re-adding the same account resyncs everything from IMAP, headers and all, from scratch.

Reordering

The order accounts appear in (both the unified list and per-account view) is a saved sort_order, changed by drag-and-drop and persisted with one call — it is purely a display preference and has no effect on sync or egress.

Limits

Available since 0.1.0 (IMAP), OAuth added the same release cycle
Role viewer can list; operator can add/edit/sync/reauth/test; owner deletes
Tier limits Community 10 · Sponsor 25 · Pro unlimited — see Plan and license
Counted per instance, not per user
Providers any IMAP server; Gmail and Outlook via the operator's own OAuth app

What it does not do

  • It offers no shared/default OAuth application. Gmail and Outlook connect buttons stay disabled until the operator registers their own app with the provider — this is a real setup step, not a missing feature.
  • Add-account validation checks reachability, not deliverability. SkimMail dials the server and lists mailboxes; it never sends a test message, because SkimMail is a read-first reader.
  • Removing an account never touches mail on the provider. It deletes only what SkimMail cached locally.
  • A changed sync depth is not retroactive in the sense of undoing what a narrower window already skipped — it changes the window the next sync uses going forward.

See also


SkimMail · skimmail@base101.app · 2026-09-18 · commit f525934

Clone this wiki locally