Skip to content

Feature Notifications

SkimMail docs edited this page Sep 15, 2026 · 1 revision

English · Tiếng Việt · 中文

Notifications

Since 1.3.0 SkimMail can tell you when something happens without you having to look: an account that stopped syncing, an egress that went down, an OAuth sign-in about to expire — and, since 1.4.0, whatever your rules decide is worth a message. Delivery goes to notification channels you configure once and every feature shares.

What this solves

Two different problems, and it is worth keeping them apart because they have different failure modes.

Operational alerts ("Watchtower") answer: is this server still doing its job? A self-hosted mail reader that silently stopped syncing three weeks ago is worse than one that is obviously down, and nobody watches a dashboard they do not have a reason to open.

Push notifications answer: did something arrive that I care about? That is a different audience — you, on a phone — and a different delivery path.

Where it is

  • Settings ▸ Notifications — push preferences, notification channels, and which Watchtower events each channel fires on. Reading the settings is viewer level; changing them is operator.
  • Rules route into the same channels: Settings ▸ Rules. See Rules and signals.

All of these settings are instance-wide, not per user: every user of the instance shares one set of channels and one set of push preferences.

The two kinds of notification

Watchtower alerts Push
Answers is the server working? did mail arrive?
Goes to channels (chat, webhook) browsers and devices
Triggered by auto-stop, dead egress, failing OAuth new mail, or a rule's push action
Quiet hours do not apply apply
Needs a channel a subscribed browser or an FCM device

An alert is not silenced by quiet hours, and that is deliberate: quiet hours are about mail, and an account that stopped syncing at 2 a.m. is still stopped at 8 a.m.

Notification channels

Settings ▸ Notifications ▸ Notification channels. Six types:

Type What you provide How the secret is held
Webhook an http(s) URL, plus an optional HMAC signing secret URL in plain text, secret encrypted
Telegram a bot token and a chat ID token encrypted
Slack an incoming-webhook URL the URL is the credential — encrypted
Discord an incoming-webhook URL encrypted
Microsoft Teams a Workflows (Power Automate) URL encrypted
Google Chat an incoming-webhook URL encrypted

Every stored secret is write-only: the API says whether one is stored, never what it is, and leaving the field blank when you save keeps the existing one. If a delivery error happens to contain the credential — a bot token inside a URL, for instance — it is stripped before the error reaches a log or the API.

The chat channels receive a plain-text rendering of the event, on purpose: no markdown and no parse modes, so an account name or a provider error message containing formatting characters cannot break or reshape the message. Teams gets an Adaptive Card envelope because the old O365 connector format was retired.

Each channel has its own enabled switch and its own Alert me when toggles, and a Send test button that posts a synthetic event and reports the HTTP status and round-trip time. Save the channel before testing it.

Watchtower alerts

Three events, each independently switchable per channel:

Event Fires when Default
account_down the circuit breaker auto-stops an account after repeated failures on
proxy_down the egress an auto-stopped account uses has derived to down on
oauth_expiring an auto-stopped Gmail/Outlook account's failure looks like an auth failure off

All three are consequences of the auto-stop circuit breaker, so they fire at the moment an account is stopped, not on every failed sync — see Sync and Sync Health for what triggers that. proxy_down is additionally deduplicated per egress with a 30-minute cooldown, so one dead proxy carrying eight accounts does not produce eight alerts.

oauth_expiring is off by default because it is a softer signal: it is inferred from the text of the provider's error, and it always arrives alongside an account_down for the same account.

Webhook payloads and signing

A Webhook channel receives a JSON POST:

{
  "event": "account_down",
  "source": "watchtower",
  "message": "me@example.com stopped syncing after 3 failed attempts",
  "account": "me@example.com",
  "account_id": 4,
  "error": "dial tcp: i/o timeout",
  "failures": 3,
  "app": "SkimMail",
  "timestamp": 1757808000
}

source is watchtower for the three events above and rule for a rule hit, whose event is rule_hit. proxy and proxy_id appear instead of the account fields on a proxy_down.

With a signing secret set, each delivery carries:

X-SkimMail-Signature: sha256=<hex HMAC-SHA256 of the exact request body>
User-Agent: SkimMail-Watchtower

Delivery is retried up to three times — on a network error or a 5xx, with a one-second-per-attempt backoff. A 4xx is final: a rejected payload is a configuration problem, not a transient one. Each request times out after 10 seconds and the whole fan-out after 20.

Deliveries go direct from the server, never through an account's egress. A webhook points at your own endpoint; routing it through a mailbox's proxy would be surprising and would leak the fact of the alert to that proxy. See Connections and per-account egress.

Push to browsers and devices

Two independent transports, either or both:

  • Web Push (since 1.5.0) — standard VAPID, no vendor account. The key pair is generated on first start and persisted, so there is nothing to configure. Override it with VAPID_PUBLIC + VAPID_PRIVATE (both, or neither) and set the contact with VAPID_SUBJECT. The browser still needs an HTTPS origin and a per-device opt-in, so a plain http:// LAN address cannot subscribe — put SkimMail behind TLS or use a tunnel plugin.
  • FCM for mobile — off unless you set FIREBASE_ENABLED=true and point FIREBASE_CREDENTIALS at a service-account JSON.

A subscription or device token that the push service reports as gone is deleted automatically on the next send, so revoked browsers do not accumulate.

Real-time updates inside an open SkimMail tab never use any of this: they arrive over the WebSocket the app already holds, which is also how a rule's in-app toast is delivered.

Quiet hours and what a notification may say

Push has a gating chain and every link must pass:

  1. a transport exists (Web Push or FCM);
  2. Enable push notifications is on — it is off by default;
  3. the account is in Active accounts, or that list is empty, meaning all;
  4. the current hour is outside quiet hours (-1 on either end disables them; the window wraps, so 22 → 7 is a night);
  5. content settings decide the text.

Content settings, in the order they win: Hide content overrides everything and produces a generic message. Otherwise Show sender and Show subject each add their part, and with both off you get a generic message too.

A rule's push action runs through this same chain, including quiet hours and content settings — a rule cannot push past a quiet window. Rule deliveries to channels are different: they are routed explicitly by channel id and are not filtered by that channel's Watchtower event toggles. Only the channel's own enabled switch applies.

Limits

Available since 1.3.0 (channels 1.4.0, Web Push 1.5.0)
Role viewer to read, operator to change
Channel types 6
Maximum channels 50
Webhook retries 3 attempts; 5xx and network errors only
Request timeout 10 s per attempt, 20 s per fan-out
proxy_down cooldown 30 minutes per egress
Rule hits per sync 20 per account, then dropped with a log line
  • Scope is the instance, not the user. Channels, push preferences and quiet hours are shared by everyone signed in to this server.
  • There is no delivery history. Failures are written to the log; nothing queues a failed alert for later.
  • "Only notify for priority mail" is stored and not yet acted on. It was built for AI classification, which is not available in this release, so nothing reads the setting today.

What it does not do

  • It does not notify per message by default. Push is off until you turn it on, and even then it says "new mail", not "here is the mail".
  • It does not store or forward your mail. A chat channel receives a rendered line about an event; message bodies never leave the server this way.
  • It does not retry past a 4xx, and it does not queue. An endpoint that was down when the alert fired misses that alert.
  • It does not alert on every failed sync — only when the circuit breaker stops an account. Transient failures are visible on the Sync Health board instead.
  • It does not send email. SkimMail has no outbound mail path, so there is no "email me when" option.

See also


SkimMail · skimmail@base101.app · 2026-09-14 · commit 76610cb

Clone this wiki locally