Snooze any inbox item
Every row in the Omniscio Inbox is snoozable — sessions, SMS conversations, Telegram chats, fired alarms, cron approvals, automation approvals, recipe approvals, recipe authoring approvals, CLI Pending Actions, RSS articles, RSS digests, drip campaigns, the Briefings digest, and weekly summaries. Snoozing temporarily hides the row from the Inbox and from every other "needs you" / sidebar / list filter until a time you pick, then the row reappears automatically.
What it is
Every row in the Omniscio Inbox is snoozable — sessions, SMS conversations, Telegram chats, fired alarms, cron approvals, automation approvals, recipe approvals, recipe authoring approvals, CLI Pending Actions, RSS articles, RSS digests, drip campaigns, the Briefings digest, and weekly summaries. Snoozing temporarily hides the row from the Inbox and from every other "needs you" / sidebar / list filter until a time you pick, then the row reappears automatically.
The mechanic is unified: one Snooze Palette (same UI as session snooze), one event (snooze-entity), one gate helper (isInboxItemSnoozed), and one expiry sweep (60-second tick). Different inbox kinds use slightly different storage on the backend, but the user-facing behaviour is identical across every row.
This is distinct from snoozing a session: session snooze drops a hoisted amber chat marker into the conversation and survives until you reply. Snoozing a non-session inbox row (an SMS thread, a cron approval, etc.) does not insert a chat marker — there is no shared conversation to mark — but it does hide the row from every list until the snooze expires or you act on it.
A snooze sticks to the alert, not to the card. Many alerts are raised by something that keeps watching a condition: it posts a card when the condition is true, clears the card when it resolves, and posts again the next time it breaks. That second posting is a brand-new row behind the scenes. Snoozing is keyed to the alert's own identity rather than to the row that happened to deliver it, so if you snooze a card for a week and the same alert is cleared and re-raised twice in the meantime, it stays hidden for the week you asked for. Three limits keep that honest: an expired snooze is never revived (a deferral cannot outlive its own end time), the longest deferral wins when more than one is on record, and this applies to alerts only — every other inbox kind already has a stable row and is unchanged.
Where to find it
Right-click any row in the Omniscio Inbox and choose Snooze... to open the Snooze Palette (presets plus a field for a time you type). With a row selected you can also press H.
How it behaves
How to use it
- Right-click any inbox row (desktop) — the row's context menu shows a Snooze... item that opens the Snooze Palette directly (there is no submenu of presets). The palette's presets follow the same contextual logic as session snooze: 2 Hours From Now, This Morning (only during 12 AM–6 AM, resolves to 8 AM the same day), This Evening, Tomorrow Morning, Tomorrow Afternoon, Day After Tomorrow, This Weekend, Next Week, End of Week, Next Weekend, Next Month. With several rows selected, right-clicking one of them snoozes the whole selection and the item reads "Snooze N selected..." — see bulk-select-sidebar.md. Right-clicking a row outside your selection still snoozes just that row.
- Pick a preset, or type a time — in the palette, picking a preset snoozes immediately. To type a custom time ("in 2 hours", "friday 9am", "tonight 11", or compact shorthand like "805p" / "8p"), type it into the palette, which is already pre-targeted at the row. The same smart AM/PM defaulting applies (hours 1–6 → PM,
tonight 9→ PM, explicit AM/PM always wins). - The row disappears from the Inbox immediately. On desktop a single snooze shows a 5-second Undo toast; a bulk snooze registers one Undo ("Snoozed N items"). The badge counts on the sidebar and the inbox group header update live via the
INBOX_SNOOZE_CHANGEDpush event. - Snoozes expire automatically. A background sweep runs every 60 seconds, clears the snooze record, and re-emits the row into the Inbox — no refetch, no refresh.
- Snoozing the row you're viewing carries you to the next inbox item — snooze is a triage gesture just like Archive, so it advances the cursor (and closes to "All clear" when it was the last item) instead of leaving the just-snoozed item's pane open. This holds for every inbox kind — sessions, alerts, SMS, approvals, drips, and the Briefings / weekly-summary cards alike. Snoozing a different row via right-click while viewing another leaves your place put. A build guard makes it impossible for any snooze surface to regress this — see inbox-snooze-coverage-contract.md
snoozing-the-open-row-advances-the-cursorand the cross-cutting inbox-navigation-contract.mdaction-advance-coverage-manifest.
- Snoozing the row you're viewing carries you to the next inbox item — snooze is a triage gesture just like Archive, so it advances the cursor (and closes to "All clear" when it was the last item) instead of leaving the just-snoozed item's pane open. This holds for every inbox kind — sessions, alerts, SMS, approvals, drips, and the Briefings / weekly-summary cards alike. Snoozing a different row via right-click while viewing another leaves your place put. A build guard makes it impossible for any snooze surface to regress this — see inbox-snooze-coverage-contract.md
- Open any inbox item and snooze from its header — every inbox detail view shows a Snooze (clock) button beside Archive, on both desktop and mobile. It's the shared InboxSnoozeButton — the twin of the Archive button — on the alert, drip, scheduled-message, SMS, Briefings and weekly-summary viewers, and the
snoozeTargetclock on the approval-shell panes (recipe run, the Telegram/RSS/Jira/Linear/Granola/Notion redirect pane, and bug-intake). Clicking it opens the same palette. This is the primary path on mobile, where there is no right-click menu; the desktop right-click Snooze... item and the H shortcut stay available too. (S is not a snooze key; on a cron failure card it toggles Auto-fix.) (The per-row mobile Snooze clock that used to sit beside the dismiss X was removed 2026-06-06 — the affordance lives in the opened-item header, not on the row; see inbox-snooze-coverage-contract.mdno-per-row-snooze-button/every-archivable-viewer-also-surfaces-snooze.alarm-firedis the one carve-out — it keeps its own "Snooze 9m" button.MobileInboxDetailHeaderwas the original intended mechanism but is now dead code.)
Special case — alarm-fired rows. A fired-alarm row already shows an inline Snooze 9m button (the legacy quick-action). The right-click Snooze... item now coexists with it: 9m stays as the one-tap default, the full palette is reachable via the context menu for any custom duration.
How it works
Inbox rows split into two storage paths based on whether the underlying entity already has a snoozed_until column on its own table:
Three reserved kinds keep their per-table snooze field (because they already had one before universal snooze shipped):
session—sessions.snoozed_until, drives the chat marker and the rich session-snooze behaviour (snooze-a-session.md).digest—daily_digests.snoozed_until, hides today's digest from the Inbox.weekly-summary—weekly_summaries.snoozed_until, hides this week's summary.
Every non-reserved inbox kind routes through the inbox_snoozes table (v200 migration). The set is DERIVED — getUniversalInboxKinds() = INBOX_SOURCE_INTEGRATIONS minus the 3 reserved kinds (inbox-snooze-kinds.ts; a LAZY getter, not an eager const, so the module is safe to import from anywhere in the inbox-source-behaviors ↔ inbox-sources ESM cycle) — so a new inbox source becomes snoozeable automatically. The set is every inbox kind in INBOX_SOURCE_INTEGRATIONS (inbox-sources.ts) except the three reserved ones (session, digest, weekly-summary), via getUniversalInboxKinds(); this page does not list them by hand because the registry is the source of truth. (The Context Size / doc-token and MCP overhead warnings moved off their own inbox kinds onto the central alert system in 2026-09 — they snooze as ordinary alert cards now, not their own doc-token-alert / mcp-bloat-alert kind.)
One special case inside alert: a Tasks reminder alert (a task's deadline / back-from-snooze notice) does NOT write an inbox_snoozes row. The backend handler detects its tasks-v2-… dedup key and instead snoozes the underlying task (sets tasks_v2.snoozed_until) and archives the reminder — so the task actually defers and the reminder doesn't just reappear. See inbox-snooze-coverage-contract.md "Known edge" and tasks-v2-contract.md inbox-reminder. Every OTHER alert (and every other non-reserved kind) routes through inbox_snoozes as below.
The inbox_snoozes row shape is (kind TEXT, target_id TEXT, snoozed_until TEXT, note TEXT?) keyed on (kind, target_id). The target_id is the item's payloadRef.id — the canonical snooze key, which can be coarser than the row's item.id (e.g. pr-merge-queue snoozes per-repo while rendering per-PR rows; intake-triage keys on intake-class-<k>/intake-issue-<id>). Keying on item.id instead silently breaks those kinds. Composite keys mean two different kinds can share an id without colliding.
Service (/src/main/services/inbox/inbox-snooze-service.ts) is a per-kind dispatcher. It rejects the three reserved kinds at the service boundary so the universal table can never accumulate rows for them (those go through their existing lifecycle services). Every write emits INBOX_SNOOZE_CHANGED so the renderer cache updates without a refetch.
Usage tracking. On a successful snooze, the inbox handler (like the digest, weekly-summary, and email handlers) records a snooze.applied event with a duration bucket via recordSnoozeUsage — feeding the always-on snooze-usage stats and the (Lab, default-OFF) personalized snooze ordering. These cross-kind paths record the bucket only; the preset ranking is session-driven. See snooze-a-session.md "Personalized ordering (Lab)" + snooze-recommendations-contract.md.
Expiry sweep (/src/main/services/inbox/inbox-snooze-service.ts checkExpiredInboxSnoozes) runs every 60 seconds via createPeriodicTask, deletes rows where snoozed_until <= now, and fires INBOX_SNOOZE_CHANGED { removed: true } for each. Runs in parallel with (not nested inside) the session snooze sweep — same cadence, separate tasks.
Renderer cache (/src/renderer/src/stores/inbox-snooze-store.ts) is a Zustand store keyed by composite kind|entityId. loadActive() pulls all non-expired rows on boot; the INBOX_SNOOZE_CHANGED push listener applies upserts and deletes incrementally.
Gate helper (/src/renderer/src/lib/inbox-snooze-gate.ts) isInboxItemSnoozed(kind, entityId) is the single source of truth every inbox/attention filter consults. For session / digest / weekly-summary it reads the entity's own store; for the universal kinds it reads inbox-snooze-store. In both cases it compares snoozedUntil > Date.now() so an expired snooze stops hiding the row immediately, even before the next 60-second sweep fires.
Lint enforcement. inbox-snooze-gate-routing.test.ts bans bare !row.snoozedUntil reads (they leave rows stuck-snoozed past expiry; non-inbox concerns like tasks are allow-listed). inbox-snooze-coverage.test.ts asserts every non-reserved kind is in the derived allow-list, every kind's projector calls isInboxItemSnoozed, and the dispatch keys on payloadRef.id. Contract: inbox-snooze-coverage-contract.md.
UI surface. Snooze fires a snooze-entity CustomEvent (the palette stays the universal entry point). Three producers, all building the payload via the shared buildSnoozeEntityDetail (inbox-snooze-kinds.ts), keyed on payloadRef.id: the desktop right-click Snooze... item (InboxItemContextMenu, which dispatches snooze-entity directly); the H shortcut (snoozeSession, which also covers the Briefings row); and — the opened-item path, desktop AND mobile — the shared InboxSnoozeButton in each detail viewer's header (plus ApprovalPaneShell's snoozeTarget on the approval-shell panes). The button resolves the open item's target through resolveActiveInboxSnoozeTarget() / snoozeActiveInboxItem() (inbox-source-behaviors.ts, the snooze twin of dismissActiveInboxItem), so it's keyed on the right payloadRef.id for every kind — the two reserved-kind viewers (digest / weekly-summary) pass an explicit target instead. Coverage is locked by inbox-snooze-button-coverage.test.ts (archive ⟺ snooze on every viewer; AlarmDetailPane carve-out). (The per-row mobile Snooze button on UnifiedInboxRow was removed 2026-06-06.) The palette is the shared SnoozePalette; its SnoozeTarget.type is 'session' | 'email' | 'digest' | 'weekly-summary' | UniversalInboxKind, where UniversalInboxKind is derived from the registry (so a new kind is covered at compile time).
Over the CLI
The same universal snooze is reachable without the UI, so a script, a hotkey or an outside AI can put a row away and bring it back on a timer of its own:
POST /inbox/snooze— body{ kind, entityId, snoozedUntil, note?, title? }. It upserts, so snoozing the same row again simply moves its wake time rather than stacking a second snooze.POST /inbox/unsnooze— body{ kind, entityId }. Brings the row back immediately, and is idempotent: unsnoozing something that is not snoozed is a success, not an error.GET /inbox/snoozes— the rows that are snoozed right now, askind,entityId,snoozedUntilandnote. Bounded by?limit=.
Three kinds are deliberately not snoozable this way: session, digest and
weekly-summary. Each carries its own snooze field on its own row rather than living in the
universal snooze table, so asking the universal routes for one is refused with a 400 that names
the kind and tells you to use that kind's own snooze instead. The UI hides this distinction
entirely — every row in the inbox snoozes the same way from the right-click menu — so it is only a
caller's concern.
Mutations are charged to the shared mutation budget and reads to the read budget, like the rest of the CLI surface.
Related
- snooze-a-session.md — sessions get the rich chat-marker behaviour on top of the gate; the snooze entry point is the session header overflow menu (or right-click on a sidebar row), not the inbox right-click.
- inbox-overview.md — every row in the Inbox is now snoozable; the snooze badge counts and group counts update live via the
INBOX_SNOOZE_CHANGEDpush event. - bulk-select-sidebar.md — Shift+J/K multi-select followed by bulk Snooze applies the same palette to every selected inbox row in a single shot.
- alarms.md — the inline Snooze 9m button on a fired-alarm row remains for one-tap dismissal; the right-click Snooze... item opens the full palette for custom durations.
Last verified 2026-10-02