---
title: Supermail
---
# Supermail

## What it is

A Superhuman-style email client - folders, compose, snooze, follow-ups, splits,
flows, and filters on top of Gmail or IMAP - brought into Omniscio as a
**native sidebar feature** and shipped from the same in-repo source as the
standalone web client at `mail.jls.dev`. It is a React port of the vendored
Supermail UI, not a webview or an embedded plugin.

> **Status (2026-08-10): one-project Supermail.** The UI lives at
> `src/plugins/supermail/ui`, the backend lives at `src/plugins/supermail/backend`,
> and both deploy from this Omniscio repo. The native Omniscio surface is off by
> default behind `supermailEnabled`; the standalone web build deploys to
> `mail.jls.dev` and uses the same backend at `mailback.jls.dev`.

## Where to find it

### How to turn it on

Settings → Connections → **Email & Summaries** → **Supermail** → **Supermail (sidebar)**
(off by default). The setting is `supermailEnabled`. While off, the sidebar row is
hidden and nothing Supermail-related loads.

A separate toggle, **Show unread in Omniscio inbox** (`supermailInboxEnabled`),
controls whether Supermail pushes unread items into Omniscio's unified inbox. It is
gated by the master `supermailEnabled` toggle.

## How it behaves

### What the user sees

- A **"Supermail"** row appears in the Omniscio sidebar (under the Omniscio built-ins
  group), with a mail icon. The row only shows when the feature is enabled.
- Clicking it opens Supermail **full-pane inside Omniscio**, with a **`Mail | Sessions`
  switch**: Mail is the mail client; Sessions lists the Claude working sessions started
  from Supermail (see "Start a session" below). In **Mail** mode the switch sits in the
  Omniscio-owned panel header (`SupermailPanel`); in **Sessions** mode it sits atop the
  session list in the sidebar. Its placement is configurable via `supermailHeaderLayout`
  (`current` / `unified` / `rail`, default `unified`).
- **Pop it out into its own window** — right-click the Supermail sidebar row →
  **Open in new window**, or press the **Supermail window** global hotkey (default
  **Ctrl+Alt+M**) to open Supermail in a separate desktop window from anywhere, even
  while Omniscio is in the background. The hotkey registers only while Supermail is
  enabled and is rebindable (or disableable) in **Settings → Keyboard Shortcuts**
  (System-wide). Under the hood this is the shared project-window pop-out
  (`openProjectWindow('__supermail__')`), so the mail-folder sidebar comes along when
  you have it pinned. In the default overlay mode (sidebar hidden) the mail view fills
  the whole window, with no empty column beside it.
- **One email in its own window — opened for you by an agent.** Separate from the
  whole-client pop-out above, a single THREAD can be popped into its own desktop
  window, so an agent can put an email in front of you and you reply to it right
  there. Several can sit side by side, and **Ctrl+W closes the one you are looking
  at** (Escape deliberately does not — it belongs to the mail UI, and closing the
  window on it would discard a half-typed reply). Re-opening a thread that already
  has a window just focuses it, and at most 8 are open at once so an agent loop
  cannot carpet your screen. Agents open one with
  `POST /supermail/threads/<id>/open` → [CLI control surface](#cli-control-surface-settings--commands).
  Details: [supermail-thread-window-contract.md](../../.claude/memory/contracts/supermail-thread-window-contract.md).
- The first screen is Supermail's own **sign-in**, which flows through the
  Supermail backend over the `agentmc://supermail/auth` deep link (the backend's primary
  scheme; `omniscio://supermail/auth` is accepted as an alias). Omniscio has no
  user accounts of its own.

### Unsubscribe in one click (+ Unsubscribed folder)

Newsletters and marketing mail — any message carrying a `List-Unsubscribe` header —
show a small **Unsubscribe** button in the reading-view message header (ordinary mail
shows nothing). One click does three things:

- opens the sender's unsubscribe page (or `mailto:`) in the browser;
- saves a **filter** that moves FUTURE mail from that exact sender to a visible
  **"Unsubscribed" folder** — out of the inbox but browsable in the sidebar, never buried
  in All Mail and never deleted;
- moves any mail already in the inbox from that sender to that same folder.

A single humanized toast confirms all three, with **Undo** that truly restores it: the swept
messages move back to your inbox and the just-created filter is removed. It is idempotent —
unsubscribing again from the same sender never creates a duplicate filter. Signed out of the
Supermail backend, the button still opens the unsubscribe page and says auto-filing needs your
account (never a silent no-op). The filter is an ordinary Supermail filter — view or remove it
in **Settings → Inbox → Filters**, and the "Unsubscribed" folder is a normal label in the sidebar.

It matches the sender's **exact email address** (a "From email" filter field), so unsubscribing
from `news@cnn.com` can never also catch a different sender like `breakingnews@cnn.com`. It
never deletes anything (move, not trash), and it fails safe — if anything ever confuses the
filter, mail stays in your inbox (visible), never silently hidden. A fully silent one-click (no
page opens) and a server-side domain block are out of scope (they need the separate Supermail
backend). One known limit: on a mailing list where the "From" is the person who posted (not the
list), it keys on that person — the visible Unsubscribed folder makes such a slip catchable.
Contract: [supermail-quick-unsubscribe-contract.md](../../.claude/memory/contracts/supermail-quick-unsubscribe-contract.md).

### Start a session (Sessions tab + from a thread)

Supermail is a **session host**: you can start real Claude working sessions from it, the
same way the Tasks and Agent Email panels do.

- **General** — the **Sessions** tab (in the `Mail | Sessions` sidebar strip) shows every
  session started from Supermail, with a **"+ New session"** button. Opening one swaps the
  main pane to its chat; the tab shows a live count badge. The tab lists **only** your email
  sessions — it does not show the general-project extras (Councils / Running Recipes).
- **With Ctrl+T** — press **Ctrl+T** anywhere in Supermail (the inbox, a thread, the compose
  box, or the Sessions tab) to start a new **blank** session in the Supermail host; it flips
  you to the **Sessions** tab so the new chat is visible. (Seeding a session _from a specific
  email_ is the "From a thread" command below.)
- **From a thread** — with a conversation open, click the **"Start a session"** button in the
  reading-view header (top-right), or run **"Start a session from this thread"** from the command
  palette — both do the same thing. A small dialog opens with an **optional first instruction**
  (e.g. "Draft a reply" — or leave it blank) and a **Start** button. Starting spawns a session
  **seeded with the WHOLE thread (every message) as context**. The thread rides along as
  _untrusted_ text (email is attacker-controlled), so it is treated as context to act on, never as
  instructions to obey. _(A session-type picker appears here only when more than one type is
  available; today there's just one, so the dialog stays this simple.)_
- **Back to a session, from the email** — once you've started session(s) from a thread, its emails
  show a **"N session"** chip in the reading-view header; click it to jump straight to the newest
  session (the Sessions tab still lists them all). The chip shows only on emails whose thread
  actually spawned a session.

Sessions run in a managed Supermail workspace (nothing touches your real repos unless you tell
the session to). Output shows in the session view — nothing is posted back into your mailbox.
This is gated by the same Supermail toggle; there is no separate switch. For agents, the
mechanics + invariants live in
[supermail-start-session-contract.md](../../.claude/memory/contracts/supermail-start-session-contract.md).

### Reading mail through your Omniscio Google connection (shared grant)

**By default, Supermail reads mail through your Omniscio Google connection** — so if you
already connected Google to Omniscio in Settings, you are never asked to sign into Google a
SECOND time inside Supermail. The toggle **Use Omniscio's Google connection for mail**
(`supermailUseAmcGmail`, default **ON** since 2026-08-14 — unified sign-in) controls this:
when it is ON (the default) and Google is connected in Omniscio Settings, Supermail reads
your inbox through Omniscio's ONE shared Google grant — the same grant that backs Gmail /
Calendar / Drive / Sheets / Docs. No second consent screen and no new Google permission (the
shared grant already covers Gmail). Turn it OFF to use Supermail's OWN separate backend
sign-in instead. The default is **self-targeting**: it only takes effect once Google is
connected in Omniscio, so a user who has not connected Google simply stays on Supermail's own
sign-in — no one is stranded, and one toggle flip reverts it.

- **The Google token never leaves the main process.** Supermail's mail calls are relayed
  through Main (`gmail:api-request`), which holds the token and is hard-bound to your own
  Gmail (`gmail.googleapis.com/.../users/me`); only mail DATA crosses to the renderer.
- **Reversible.** Turn the toggle OFF to return to Supermail's own sign-in. It also
  auto-falls-back (to Supermail's backend / demo path) whenever the shared grant is not
  connected or Supermail runs outside the Omniscio desktop app — so it never leaves a dead
  inbox; with no other session, Supermail shows its own sign-in screen.
- **An expired Google sign-in says so, with a one-click fix.** When the shared Google sign-in
  has EXPIRED (not merely never connected), Supermail's sign-in screen says **"Your Google
  sign-in has expired"** and, on the computer, offers **Reconnect Google** — the same
  reconnect Omniscio's own Gmail panel runs — which brings you straight back to your inbox. On
  a phone it says to reconnect on your computer, because Google's sign-in opens a browser
  there. A background label refresh that fails in the meantime just keeps the labels you have
  (it is no longer reported as a crash). Invariant: `an-expired-grant-offers-reconnect-google`.
- **A failed send is recoverable in one click.** If a send (or other write) can't authorize
  because the shared Google grant went stale or is missing send permission, Supermail no longer
  fails silently — a persistent **"can't send email — reconnect Google"** card appears in your
  inbox with a one-click **Reconnect Gmail** button (and the Google Account settings card gains a
  **Reconnect** button while connected). It clears itself the moment a send succeeds; an ordinary
  network / rate-limit / 5xx hiccup never raises it. See [inbox-alerts.md](inbox-alerts.md).
- **Scope.** Covers the core inbox (list, read, search, archive, mark-read, label, send
  — including choosing which of your Gmail **"Send mail as"** identities a message goes out
  from, via the composer's **From** picker (read from Gmail settings through the relay; it
  quietly falls back to just your primary address if the connection predates the needed
  permission — reconnect Google via the sidebar's **Reconnect** to enable your other
  addresses), snooze) — plus viewing and downloading **received attachments** (inline images render in the
  body, image attachments show a thumbnail, and downloads save the real file; the bytes are
  fetched from Gmail via the relay) and, via the local mirror, background sync, exact per-lane
  split-counts, offline reading, and local search (see the mirror bullet below). Backend-only
  extras (multiple mailboxes, drafts, scheduled send, attachments-on-**send**) stay on Supermail's
  own backend and are degraded in this mode. Engineering invariants:
  `.claude/memory/contracts/supermail-shared-gmail-grant-contract.md`.
- **Works on your phone (mobile web).** With this toggle ON, your inbox now loads and is fully
  usable from the Omniscio mobile web UI (over Tailscale) — read, archive, reply, and send. It reads
  from the desktop's synced mirror and signs in off the shared grant, so **no mailbox key is sent to
  the phone**. For safety a paired phone is bounded to those triage actions: it CANNOT permanently
  delete mail or change Gmail forwarding/filters (those stay desktop-only, refused by a remote
  allowlist), and only your **approved, signed-in** device can reach it. See invariant 4 of the
  shared-grant contract.
- **A send that needs a Google reconnect fails LOUDLY, never silently (F162).** In this mode a
  reply whose Gmail send returns 401/403 (the shared grant lacks the send scope) surfaces a
  **persistent**, actionable "reconnect your Google account in Settings" toast and **restores your
  draft** — not a vague, disappearing "try again", and never a silently-lost reply. It does NOT tear
  the read session down (the inbox keeps working while you re-consent); reconnecting Google in
  Settings restores sending. Invariant:
  `.claude/memory/contracts/supermail-resilience-contract.md` (the F162 bullet).
- **Full inbox, not just the first page.** The shared-grant connector pages the inbox with
  Gmail's own `nextPageToken` cursor (`getThreadsPage` in `shared/gmail-api/gmail-api.ts`), so
  the list loads your WHOLE inbox as you scroll — not a single ~25-thread page. (Before this it
  fetched one page and dropped the cursor, so a large inbox showed only ~25 mails with no way
  to load more.) Each listed thread still costs a `threads.get`, so this direct path is heavier
  than the backend's server-side mirror; a **local mirror for the shared-grant path** (instant
  cold-load, offline reading, background sync, local search, exact counts) landed as the next
  bullet. Split-tab counts on this path are now **exact over the local mirror** — tallied from the
  whole local copy, not just the ~one loaded page (Gmail can't cheaply count arbitrary custom
  lanes, so the mirror does it), falling back to the loaded-page estimate only before the copy fills.
- **Local mailbox mirror (instant load + offline + always-on background sync).** On this path
  the inbox reads from a **local per-account copy of your mailbox** kept in the app's main
  process (a standalone SQLite file), so it paints instantly and works offline — falling back
  to the live Gmail connection whenever the copy is cold (never a dead inbox). Main owns the
  account (the renderer never names it), the copy is **wiped when you sign out of Google** (it
  holds your mail), and the initial fill runs low-priority + backs off under load, so it never
  contends with your machine. A **background sync engine keeps the copy fresh even when the
  Supermail panel is closed** — a light ~5-minute check that pulls only what changed via Gmail's
  `history.list` (from a saved bookmark), applies new/removed mail, and self-heals if the
  bookmark ever expires; it trims (never fully stops) under machine load and never triggers the
  big initial download on its own. **When a background check lands new mail it now wakes the
  on-screen inbox to refresh itself** — a payload-free nudge from the main process to the panel
  that re-fires the inbox's normal refresh — so freshly-synced mail appears on its own, instead
  of waiting for the next time you focus or reopen Mail. **And changes you make in Gmail or
  Superhuman itself** — archive, star, mark-read — now surface in **seconds, not minutes**: on
  top of the ~5-minute check, the copy also syncs the moment you're about to look at Supermail —
  when you open or switch to it, when you bring the app back to focus (e.g. returning from your
  browser), and on a ~60-second loop while you're watching it side-by-side with your browser.
  **On top of that, the open inbox re-reads the local copy on its own about every 30 seconds** — so
  an email you archive (or star / mark read) in Gmail or Superhuman disappears from the list, and the
  tab counts update with it, **on their own — even if you never click into Supermail or switch
  windows** (that periodic self-refresh pauses while Supermail is hidden and reads only the local
  copy, so it stays free). Every one of those is coalesced (single-flight + a short throttle) and
  self-gates when the mirror isn't in use, so it stays cheap and never over-fetches. **When Supermail
  is enabled, both the panel and this local copy are warmed a few seconds after the app launches** — so
  even the _first_ time you open Supermail in a session it appears instantly with already-fresh mail,
  instead of pausing to load and sync (only on the shared-Google path with an account connected; off
  when Supermail is disabled or "keep panels loaded" is off). Off-switches:
  `AMC_DISABLE_SUPERMAIL_MIRROR_BACKFILL` (the
  initial fill) + `AMC_DISABLE_SUPERMAIL_MIRROR_SYNC` (the background sync) +
  `AMC_DISABLE_SUPERMAIL_MIRROR_BODY_BACKFILL` (offline-body caching) +
  `AMC_DISABLE_SUPERMAIL_MIRROR_SEARCH_INDEX` (the local search index). This is **Stage 2a**
  (local copy + instant load) + **Stage 2b** (the always-on background sync) + **Stage 2c**, which
  layers three things on top: **instant thread open + offline message bodies** — opening a thread now
  paints its body **instantly from the local copy** (no more ~0.25–0.5s blank while it waits on
  Gmail), then quietly upgrades to the full live thread with attachments / cc / bcc in the
  background; those same cached bodies also let a thread open with **no connection at all**, and the
  visible inbox rows are preloaded from the local copy so clicking through stays instant (if a
  message's body isn't cached yet, it just opens the old way rather than flashing an empty email);
  **local search** — subject / sender / body full-text search AND the common operators
  (`from:` / `to:` / `subject:` / `is:unread` / `is:starred`) answered instantly from the local copy,
  falling back to a live Gmail search only for operators it genuinely can't evaluate (e.g.
  `has:attachment`, date ranges, other folders), a negated operator, or when you choose "Search all
  mail"; and **exact per-lane split-tab counts** over the whole local
  copy. Like the fill and the sync, the body-caching and search-indexing are paced, back off under
  machine load, and are each individually off-switchable — they never contend with your machine.
  Invariants: `.claude/memory/contracts/supermail-local-mirror-contract.md`.

### New-mail notifications (which producer runs, and why it varies)

When mail arrives, the desktop alert comes from **one of two producers**, chosen by which
sign-in you use — never both:

- **Reading through your Omniscio Google connection (the default, `supermailUseAmcGmail` ON)**
  — the alert comes from your **local copy of the mailbox**, which is already kept fresh by the
  background sync described above. **No Supermail backend session is involved anywhere in the
  path.**
- **Supermail's own sign-in (the toggle OFF)** — the alert comes from Supermail's backend
  notification stream, exactly as before. Your mail is not kept locally in that mode, so there
  is nothing local to notify from. **Nothing about this path changed.**

**Why the split exists.** The backend stream is keyed to a session token with a 30-day life
that nothing renews — and it cannot be renewed without a Google consent screen, so no
automation can do it for you. When it lapsed, mail kept syncing perfectly while *notifications
alone* silently stopped, behind a repeating "notifications are disconnected" alert. Reading
from the local copy removes that session from the notification path entirely, so there is
nothing left to expire. In this mode the backend stream stands down and any standing
"disconnected" alert is cleared, because in this mode that alert is no longer true.

**Behaviour you can expect:**

- **Freshness.** Alerts arrive on the next sync. The sync runs **every 5 minutes**, and speeds
  up to about **every 60 seconds while you are actively at the computer** (it backs off on its
  own when the machine is idle or busy). The
  Settings → Email & Summaries → **Notifications** preferences — desktop on/off, the Important-only
  filter, and quiet hours — apply to these alerts.
- **Unread mail from other people only.** A thread you have already read, and a note you mail
  yourself, never produce an alert — the same rule the backend stream applies.
- **One alert per conversation.** Several new replies in one thread produce a single alert, not
  several.
- **A big backlog can never flood you.** A burst of new mail alerts on at most a handful of the
  newest threads and collapses the rest into one silent "N more new emails" summary.
- **It is impossible to double-alert.** A mail pass that overlaps a previous one (the sync
  resumes from its boundary point after a large backlog) cannot re-alert the same mail.
- **The "Emails in inbox" option keeps working** in this mode — each new email still creates its
  dismissible Omniscio inbox card with the inline reply box.

One honest limitation: the **"VIP only"** notification filter currently results in no alerts,
in this mode and on the backend stream alike — the backend reports VIP as always-false, so the
setting is inert today. Engineering invariants:
[supermail-notification-delivery-contract.md](../../.claude/memory/contracts/supermail-notification-delivery-contract.md).

### Superhuman snooze-reminders are filtered out

If you also use **Superhuman**, its snooze feature brings a thread back by mailing you a
"Reminder" note from `reminder@superhuman.com` ("This is a reminder from Superhuman Mail. Do
not reply to this email."). Supermail now treats that reminder as **noise — exactly like its
own snooze-return marker**: it never becomes the inbox row's sender/preview and never shows
inside the opened thread, so the row keeps the **real person and message** instead of
"Reminder". The conversation itself **stays** in the inbox (at its real date, with no
artificial bump); only the reminder message is hidden. Detection is by **sender**, scoped to
the snooze reminder only (a one-off `sharing@superhuman.com` note is a different Superhuman
feature and is left alone). Existing reminder-topped threads clean up on the mirror's next
self-healing rebuild. Engineering invariants: the local-mirror contract (`thread-we-snoozed-never-evicted`) in
[supermail-local-mirror-contract.md](../../.claude/memory/contracts/supermail-local-mirror-contract.md).

### Filters (your own auto-acting rules)

Supermail has its **own filter rules**, separate from Gmail's filters, that **automatically act on
incoming mail**. Manage them at **Settings → Inbox → Filters** (a master on/off toggle + a "Manage filters"
launcher).

- **Start one from a message (`T`, right-click, or the funnel button)** — press **T** on a focused
  inbox thread (or the open conversation), **right-click any message row and pick "Filter messages
  like these"**, OR click the **funnel button** in the open email's top-right controls (all three
  share the same `openWithSeed` seed path), to open a new filter **pre-seeded with that
  sender** (`From is exactly …` — Gmail-style "filter messages like these"); you choose the action
  and save. The editor opens over the inbox (the manager is mounted app-level and driven by
  `filter-manager-store`), not only from Settings. Bare `T` is global; the seed carries no action so a
  real rule requires you to finish it.
- **Apply a new filter to your existing inbox on create.** When you start a filter from an email, the
  editor offers **"Also apply to matching emails already in my inbox"** — ON by default for that flow
  (OFF for a blank "New filter" from Settings). Creating the filter then sweeps your **whole current
  inbox** with that one new rule (not just future mail), refreshes the list, and reports the count; a
  Trash-bearing sweep is confirmed first and the button shows a loading state. This is a **manual,
  one-shot** apply (`runManualOnInboxForRule` → the runner in `manual` mode) — it does NOT weaken the
  no-retroactive-sweep rule for the AUTO path (`no-retroactive-sweep`).
- **A rule is "conditions → actions."** Match on sender, recipients (to/cc/bcc), subject, **full
  body**, sender domain, labels, **date ranges**, attachments (type/size), message state
  (unread/starred/muted/snoozed/has-link/calendar), and **whether it's a mailing list** (the message
  carries a List-Unsubscribe header) — each text condition can be **contains, exact, regex, or
  fuzzy**, combined with **AND / OR / NOT**.
- **Actions:** archive, add/remove label, move (= label + archive), mark read/unread, star, mute,
  **snooze until** a chosen delay, and **mark as spam** — all reversible, so they apply automatically.
  **Forward to an address** and **auto-reply with a Quick Reply** also apply automatically, but only
  behind a safety guard (next bullet). **Trash is manual-only** — never applied automatically (owner
  safety decision).
- **Auto-forward and auto-reply are guarded.** Because they send real email, they never fire on a
  mailing-list or no-reply message, on a thread you've already replied to, or to yourself; they send
  at most **once per sender within a day** and stop at a **daily send cap**. Like every auto action
  they never touch your existing inbox on enable, and a "Run on inbox now" that would send email is
  confirmed first. Note the whole engine runs **client-side while the app is open**, so auto-send is
  best-effort during that window, not a 24/7 server responder.
- **When they run:** automatically on new mail as it syncs in (while the app is open), plus a
  manual **"Run on inbox now"** for mail you already have. Enabling filters NEVER retroactively
  sweeps your existing inbox — only genuinely new mail is auto-acted, and each message is acted on
  once.
- **Where it lives:** your **rules** now live on the Supermail **backend** (like Split Inbox and
  Flows), so they **sync across your devices, the `mail.jls.dev` web client, and AI agents**. Only
  the **matching runs client-side** — that is what lets a rule use regex or fuzzy text (which a
  database can't evaluate). The master on/off toggle stays a per-device local switch.
- **Manage them from an AI agent / CLI.** An agent hitting the AMC control server can **list, create,
  edit, delete, enable, and reorder** your filter rules headlessly, and they sync straight to the app
  — see the amc-control `supermail-filters` skill spoke. (Running rules over the inbox from the CLI is
  a planned fast-follow; for now use the in-app **"Run on inbox now"**.)
- Engineering invariants (idempotency, fail-closed deep matching, no-retroactive-sweep, regex safety,
  reversible-**or-guarded-send** auto actions + the send-guard, backend storage + row-level security,
  the CLI proxy): `.claude/memory/contracts/supermail-filters-contract.md`.
  Code: `src/plugins/supermail/ui/src/features/filters/` + the backend `src/protected/filters/`.

### AI Filtering (the AI-powered sibling)

Alongside the rule-based filters above, an in-development **AI Filtering** feature lets AI triage
each new email against a plain-language instruction (keep / archive / label / mark-read / star —
never trash), preview-first and off by default, cost-capped, mirroring the Inbox Pilot's evaluator
for email. Full details: [supermail-ai-filtering.md](supermail-ai-filtering.md).

### Split Inbox (focused lanes, with an on/off switch)

Supermail can divide the inbox into **split lanes** (Primary, Other, VIP, Team,
Calendar…) shown as tabs above the list. A **master on/off switch** turns the whole
feature off for a single **unified inbox**, and back on to restore it. It lives in TWO
places: **Settings → Inbox → Split Inbox → "Split inbox"**, AND right in the **"Manage split
inboxes"** dialog (the ⚙️ gear next to the tabs) as a **"Turn off split inbox"** button —
because the gear is where users actually look to get rid of the tabs (the tab strip alone
only switches lanes). Turning it off from the dialog also closes the dialog so the
now-tabless inbox shows immediately.

- **Off = zero split inboxes.** The list shows ALL inbox mail as one stream, the tab
  strip (and its settings gear) is hidden, and split scoping is skipped. Your
  configured splits are **not deleted** — they stay on the backend and reappear the
  moment you switch it back on. Re-enable from **Settings → Inbox → Split Inbox** (the
  in-inbox gear is gone while off, so Settings is the way back).
- **Default OFF** — a single unified inbox out of the box; splits are opt-in (owner
  decision: a single inbox is the saner default, and the tabs were confusing users into
  thinking mail was "missing" when it was only scattered across lanes). **Device-local**
  (browser localStorage `supermail:split-inbox-enabled`), like the other display prefs, so
  it doesn't sync to the `mail.jls.dev` web client. Store:
  `features/split-inbox/split-enabled-store.ts`.
- **GOTCHA (agents):** when off, BOTH the render view-key (`inbox-page.tsx`) AND
  `currentInboxViewKey()` (`features/split-inbox/inbox-view-key.ts`) must force the
  plain `label-inbox` view — otherwise a focus/sync refetch pulls a stale split
  partition and shows a _subset_ as "all mail". The two gates are a pair; the refetch
  half is locked by `inbox-view-key.test.ts` ("targets the plain inbox when OFF").
- Manage the lanes themselves at **Settings → Inbox → Split Inbox → "Manage split inboxes"**
  (add/edit/reorder/remove custom splits; the four Gmail-category builtins + the
  catch-all are system-managed).
- **Manage them from an AI agent / CLI.** An agent hitting the AMC control server can **list,
  create, edit, delete, and reorder** your split inboxes headlessly (`/supermail/splits*`, a thin
  main→backend proxy — apply-immediately, global-token-only, the JWT stays in Main), so you can ask
  an agent to "set up a split inbox for me" — see the omniscio-control `supermail-splits` skill spoke.
  Invariants: [supermail-splits-cli-contract.md](../../.claude/memory/contracts/supermail-splits-cli-contract.md).

### Inbox list: the View options menu (sort · group · unread)

A single **"View options"** menu (a sliders button in the inbox toolbar) holds three
device-local list toggles — **sort direction**, **group by sender**, and **unread only**.
Each is a stay-open checkbox row, so you can flip several without the menu closing; all
are remembered across restarts and default to today's behaviour, so an untouched inbox is
unchanged:

- **Sort direction** — flip the list between **Newest first** (default) and **Oldest
  first**. It reorders the mail **already loaded** in the inbox; more history keeps
  loading as you scroll, exactly as the inbox fills today (it does not force-pull the
  whole mailbox). Auto-load-on-scroll stays tied to the natural newest-first flow, so
  oldest-first can't trigger a runaway "keep loading" loop.
- **Group by sender** — cluster the inbox under a **header per sender** instead of the
  date separators. Within each sender the emails stay **date-ordered** (the sort
  direction controls that order too), and every row still shows its own date — so you
  see sender AND date ("sort by sender, then by date"). Off by default (the flat
  date-grouped list). Grouping keys on the sender's email, so two different people who
  share a display name stay in separate groups.

- **Unread only** — the same one-click "show only unread" filter that used to be a
  standalone toolbar button, now the third row of the menu (`inbox-store.listFilter`).
  While it is on, the list pages do **not** auto-open the top row in the reading pane —
  you pick the thread. Opening a thread marks it read, so an unattended pick would drop
  that row straight back out of the filtered list and take the next one with it: the
  list read itself away and the panel crashed on React's nested-update limit
  ("Something went wrong"). The guard is `readingPaneConsumesFilter` in
  `features/inbox/inbox-filters.ts`, honoured by both `inbox-page.tsx` and
  `labels/label-page.tsx`; every other quick-filter (starred / important / no-reply) is
  inert to reading a thread and keeps its auto-select.

Sort + group are also **command-palette actions** (`Ctrl/Cmd+K` → "Inbox: Toggle sort
direction" / "Inbox: Toggle group by sender"); no dedicated single-key shortcut in v1.
Device-local (`supermail-inbox-view-prefs`), like the other display prefs, so they don't
sync to the `mail.jls.dev` web client. Sort/group are a **display-only transform** — the
inbox store's canonical order and its pagination are untouched — in
`features/inbox/inbox-view-order.ts` + `features/inbox/inbox-view-prefs-store.ts`, with
the menu built on the shared `Dropdown` in `features/inbox/inbox-view-menu.tsx` and the
sender-aware list headers in `features/inbox/thread-list.tsx`.

## For agents

### Settings: seven tabs, not one long page

Supermail's own Settings (the `/settings` route inside Supermail — reached from the nav,
or the **Settings** command in the palette) is grouped into tabs rather than the single
sixteen-section scroll it used to be. Every path in this library that reads
**Settings → <em>tab</em> → <em>section</em>** refers to this page, NOT Omniscio's main Settings.

| Tab               | What lives there                                                                                                                                                                           |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **General**       | Personalization (view presets, sidebar / reading pane / contact pane, hover quick actions, reading layout, replay the keyboard tour) and Confirmations (Undo toast, blank-subject warning) |
| **Reading**       | Toolbar buttons (the three-way Shown / Menus only / Hidden control) and Triage Actions                                                                                                     |
| **Inbox**         | Split Inbox, Filters, AI Filtering (when revealed), Archive Insights, Contact groups                                                                                                       |
| **Composing**     | Sending (default send behavior + shortcut rebinding) and Snippets                                                                                                                          |
| **Keyboard**      | Left-handed navigation, hover-to-act                                                                                                                                                       |
| **Notifications** | Desktop notifications + quiet hours, Blocked domains                                                                                                                                       |
| **Account**       | Sign out, and the Danger zone (reset account data)                                                                                                                                         |

Two things worth knowing:

- **Only the open tab is mounted.** Sections on other tabs are genuinely absent from the
  DOM, not hidden with CSS — so a test or a script looking for a setting must open its tab
  first, and opening Settings no longer wires up every store on the page.
- **The tab does not persist.** Settings always opens on **General**; the active tab is view
  state, not a saved preference. Escape still returns you to wherever you opened Settings from.

The strip is a real WAI-ARIA tablist: arrow keys move between tabs (wrapping at both ends),
Home / End jump to the first and last, and only the selected tab is in the tab order.

## Related

- [Supermail (part 2)](supermail-part-2.md) — the continuation of this page.
- [Supermail (part 3)](supermail-part-3.md) — the continuation of this page.

Each Supermail feature has its own page: AI Filtering, Catch me up, Assistants, Archive Insights, the AI Digest, Contact Groups, send shortcuts and the reading view.
