-
Notifications
You must be signed in to change notification settings - Fork 0
Feature Notifications
English · Tiếng Việt · 中文
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.
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.
- 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.
| 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.
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.
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.
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.
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 withVAPID_SUBJECT. The browser still needs an HTTPS origin and a per-device opt-in, so a plainhttp://LAN address cannot subscribe — put SkimMail behind TLS or use a tunnel plugin. -
FCM for mobile — off unless you set
FIREBASE_ENABLED=trueand pointFIREBASE_CREDENTIALSat 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.
Push has a gating chain and every link must pass:
- a transport exists (Web Push or FCM);
- Enable push notifications is on — it is off by default;
- the account is in Active accounts, or that list is empty, meaning all;
- the current hour is outside quiet hours (
-1on either end disables them; the window wraps, so 22 → 7 is a night); - 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.
| 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.
- 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.
-
Sync and Sync Health — the auto-stop that produces
account_down - Rules and signals — the other producer that routes into these channels
-
Connections and per-account egress — the
proxy_downsignal, and why deliveries go direct -
Configuration —
FIREBASE_ENABLED,VAPID_PUBLIC,VAPID_PRIVATE,VAPID_SUBJECT - Users and roles — who can change these settings
SkimMail · skimmail@base101.app · 2026-09-14 · commit 76610cb