Murat234 downloadsSend vault reminders and captured app notices to ntfy with auto scan, editable queues, delayed notifications, repeats, and optional AI webhooks.
This is a lightweight Obsidian plugin for ntfy notifications and optional AI webhooks.
Why ntfy: Obsidian mobile plugins cannot directly create Android system notifications. This plugin sends reminders to an ntfy topic, and the Android ntfy app shows the notification in the system notification shade.
ntfy.Use a long random topic if you use the public server https://ntfy.sh, for example obsidian-notify-9c4f0b2d7a1e4b90.
The plugin includes an interface language selector in settings. It can follow Obsidian/system language or be set manually.
Current built-in language targets:
The original Chinese/English UI remains the complete baseline. Other languages cover common settings, manager labels, command names, status labels, and notices first; any missing string falls back to English instead of showing a broken label.
The plugin scans unchecked tasks and standalone reminder lines in Markdown files.
- [ ] Call patient family ⏰ 2026-06-21 18:30
- [ ] Submit form 🔔 2026-06-22 09:00
- [ ] Review note @remind(2026-06-23 21:15)
- [ ] Review chart #remind 2026/06/23 21:15
notify:: 2026-06-24 08:30 Take medicine
ntfy:: 2026.06.24 08:30 Take medicine
提醒:: 2026-06-24 08:30 查房前准备
It also understands Obsidian Tasks plugin Emoji Format on open task lines. It uses this priority:
⏳ scheduled📅 due🛫 start- [ ] Tasks scheduled example ⏳ 2026-06-24
- [ ] Tasks due example 📅 2026-06-24
- [ ] Tasks start example 🛫 2026-06-24
- [ ] Tasks metadata is cleaned from notification text 🔁 every week ⏳ 2026-06-24 📅 2026-06-25 ➕ 2026-06-20
Dates without time use the default time from plugin settings. The default is 08:00.
- [ ] Weekly review ⏰ 2026-06-28
The status bar item shows ntfy: queue/cache. Click it, or run:
Ntfy Notifications: Open ntfy notification manager
Ntfy Notifications: Schedule delayed notification
The manager opens inside Obsidian. It does not jump to the ntfy web page.
The manager opens as an Obsidian main editor tab, similar to a normal plugin work view, not as a floating web page or sidebar. Three icon tabs stay visible:
待处理: timed tasks scanned from notes and Tasks. It shows all timed items by due time, whether they are pending, queued, delivered, or notification-off. Each item has notification on/off controls.已完成: completed vault tasks, grouped by timed and untimed items.整库待办: read-only vault task summary: open tasks with time, open tasks without time, completed tasks with time, and completed tasks without time.The 设定通知 section is collapsed by default and is only for creating a manual queued notification. It uses numeric days / hours / minutes / seconds inputs for both delay and repeat interval.
The status bar shows ntfy local/total, where local is the number of scheduled queue items not yet handed off to ntfy, and total is the full managed notification count.
Time badges in the manager are editable. Click a pending or queued reminder time to change it; note-backed reminders update the source line, cancel the old ntfy schedule when supported, and submit the replacement schedule immediately.
In the editor, type ntfy , 提醒 , notify , remind , or ⏲ to open minute-level reminder suggestions such as 30m, 1h, today 09:00, and tomorrow 09:00.
New dated tasks are added by the automatic scanner. Tasks inside the configured ntfy scheduling window, default 3 days, are handed off to ntfy immediately as scheduled messages. Manual and API reminders also attempt this handoff as soon as they are created or edited. Tasks with an explicit hour/minute are scheduled for that time. Date-only tasks, daily-note tasks without a time, and overdue unsent tasks are scheduled for the daily batch at 08:00 by default. Stable delivery records prevent the same reminder from being submitted repeatedly.
Repeating notifications are handled by Ntfy Notifications' local queue. ntfy scheduled delivery is one-shot; after a repeating item is sent successfully, the plugin calculates and queues the next due time.
Auto scan is enabled by default. While Obsidian is running, the plugin scans notes on the configured interval, keeps future reminders in the editable local queue, and hands queue items to ntfy only when they are close to due time. In daily-note folders such as 日记, tasks without an explicit time use the date in the daily note filename plus the default time.
The manager can refresh scheduled messages already handed off to ntfy and can cancel those scheduled ntfy messages when the server supports the ntfy delete API. Scheduled messages are published with a stable X-Message-ID so they can be matched later. Turning off a reminder with the bell button or editing the same task line cancels the old scheduled ntfy message before the new state is saved.
ntfy remains responsible for delivery while Obsidian is closed. When Obsidian is open, the plugin also keeps a local due-time timer and shows the same reminder through Obsidian's Notice UI once, with persisted local-delivery state to avoid duplicate popups.
Obsidian/plugin notices are captured by wrapping Obsidian's Notice API after this plugin loads. This covers most later notices, but a plugin that cached its own Notice reference before Ntfy Notifications loaded may not be captured.
ntfy supports scheduled delivery. Ntfy Notifications can send delayed messages using ntfy's scheduled delivery headers.
Standalone delayed reminder lines:
ntfy-in:: 00:30:00 Check again
notify-in:: 30m Check again
remind-in:: 2h Call back
提醒后:: 30分钟 查房前准备
稍后提醒:: 30秒 Quick check
Inline task delay:
- [ ] Check lab result ⏲ 00:30:00
- [ ] Call back after:30m
- [ ] Quick check 后:30秒
Delay formats:
HH:MM:SS
MM:SS
30s / 30秒
30m / 30分钟
2h / 2小时
1d / 1天
There is also a command:
Ntfy Notifications: Schedule delayed notification
This command opens the internal Obsidian notification manager.
Official ntfy scheduled delivery supports X-Delay, Delay, X-At, At, X-In, and In.
According to the official ntfy publish docs:
10 seconds.3 days.message-delay-limit.Ntfy Notifications uses two windows:
Maximum future days: how far ahead the plugin may hand off messages to ntfy. Keep this at 3 for public ntfy.sh.Local queue lookahead days: how far ahead the plugin keeps future reminders that are outside ntfy's scheduling window in Obsidian's editable local queue.For long-term reminders, keep them in the local queue and let later scans hand them off when they enter the ntfy scheduled-delivery window.
<your vault>/.obsidian/plugins/android-ntfy-notifier
Ntfy Notifications.https://ntfy.sh3 for public ntfy.shNtfy Notifications: Send test notification.The plugin ships as readable JavaScript and has no compilation step. Run the source checks with:
npm run check
npm test
The AI webhook is optional and disabled by default. When configured, the plugin sends a JSON event for each reminder:
{
"source": "obsidian-ntfy",
"type": "reminder",
"scheduledWithNtfy": true,
"reminder": {
"text": "Review chart",
"due": "2026-06-23T13:15:00.000Z",
"dueLocal": "2026-06-23 21:15",
"file": "Tasks.md",
"line": 12
}
}
Set AI webhook token if the receiving service expects a Bearer token.
android-ntfy-notifier for existing installs.YYYY-MM-DD, YYYY/MM/DD, YYYY.MM.DD, and Chinese date separators.08:00 time.ntfy-in:: 00:30:00 message.08:00, then handed off to ntfy when inside the scheduling window.fetch if Obsidian's requestUrl is unavailable.At header when local queue items are inside the configured handoff window. The public ntfy.sh service defaults to a 3-day maximum scheduled delay, so the plugin defaults to scheduling only the next 3 days.Notice API after Ntfy Notifications has loaded.Version 1.0.0 keeps the existing ntfy reminder queue, delayed delivery, repeating reminders, scheduled-message cancellation, review flow, inbox, deduplication, and quiet queue. Every built-in provider can have multiple named accounts with independent credentials, send/receive state, configuration, cursors, and a per-account test action.
Channel status now separates required-field completion, credential verification, runtime receive connection, and proactive-send readiness. It records redacted failures plus the latest inbound/outbound activity, refreshes the account summary as soon as edited values are saved, and suppresses repeated identical connection errors. Realtime connections and low-frequency polling stay active while Obsidian remains runnable in the background by default; mobile operating systems can still suspend or stop Obsidian, and returning to the foreground reconnects immediately.
Replies from the inbox use the destination carried by the received message, so a receive-only account can answer its original conversation without duplicating that conversation ID in proactive-send settings. An open manager view also refreshes its inbox count and message list as messages arrive.
Feishu inbox replies use the official reply endpoint for the original message and require a real returned message ID before reporting success. This keeps replies attached to the source message and prevents malformed or incomplete API responses from being treated as successful delivery. When no proactive destination is configured, normal tests, reminders, and API notifications send a new message to the latest received conversation; explicit inbox replies still use reply semantics. If neither target exists, the plugin reports the missing destination instead of claiming that a message was sent.
The hub uses one default channel. Cancip, other Obsidian plugins, and external agents only use the default route unless they explicitly pass channel IDs or request a broadcast. Enabling several connections therefore does not unexpectedly send every message to every service.
Telegram, Feishu, WeCom, Discord, Slack, Matrix, QQ Bot, and Email accounts can reuse the destination from their latest real inbound conversation when their credentials are complete but no static proactive target is configured. Channel status marks this route explicitly. Broadcasts report partial with ok: false if any selected channel fails, while preserving each per-channel result so callers never mistake partial delivery for full success.
Built-in send channels are:
The plugin does not load OpenClaw, a QQ-to-HTTP bridge, personal WeChat, desktop daemons, or provider-specific npm packages. QQ uses the official Bot access-token API, message API, and Gateway directly. Old official QQ Bot settings saved by 0.6.2 are restored automatically; retired OpenClaw and personal-WeChat settings remain preserved but inactive.
Email is an optional HTTP contract rather than an embedded mail service. It only works when the user already has a compatible HTTPS mail gateway. No mail package or background process is installed by this plugin.
The plugin exposes one normalized inbound message format through plugin.api.receive(input). It stores a bounded inbox, checks the contact/group allowlist and attachment size, deduplicates messages, records redacted connection logs, emits an incoming event, and exposes registered-handler APIs. Model selection, session creation, and reply policy belong to the consuming plugin rather than Ntfy Notifications.
Each account reports its real receive mode:
poll: every configured ntfy, Telegram, and Matrix account is polled independently while Obsidian is running; background polling can be disabled.socket: Feishu/Lark app persistent connection, Discord Bot Gateway, Slack Socket Mode, and QQ official Bot Gateway connect directly while Obsidian is running; background sockets can be disabled.relay: Feishu/Lark webhook mode, WeCom, Email, generic Webhook, Discord webhook mode, and Slack webhook mode poll a user-configured HTTPS callback relay.disabled or unconfigured: the plugin does not claim that receiving is active.Obsidian mobile cannot expose a reliable public callback server. Callback-based providers therefore use this small HTTPS relay contract:
GET <receiveUrl>?channelId=<channel-id>&cursor=<cursor>&limit=50
Authorization: Bearer <receiveToken>
Accept: application/json
The relay returns either a message array or:
{
"messages": [
{
"id": "provider-message-id",
"sender": "sender-id",
"text": "message",
"conversationId": "conversation-id",
"receivedAt": "2026-08-02T00:00:00.000Z",
"attachments": [],
"metadata": {}
}
],
"nextCursor": "next-cursor"
}
Public relay URLs must use HTTPS and configure a receive token; plain HTTP without a token is accepted only for localhost, 127.0.0.1, or ::1. The relay is responsible for validating provider signatures and decrypting provider callbacks before returning normalized messages. Provider metadata may include channelId, threadTs, qqTargetType, qqTarget, feishuReceiveIdType, feishuReceiveId, wecomTargetType, wecomTarget, or emailReplyTo so replies return to the original conversation.
Telegram polling requires the bot not to be owned by another getUpdates consumer and not to have an active webhook. Feishu/Lark app receive uses the official persistent connection and requires im.message.receive_v1, persistent-connection delivery, granted permissions, and a published app version. Discord Bot receive requires the application's Gateway and Message Content intent permissions. Slack receive requires Socket Mode, an xapp- app token, a bot token, and the relevant Events API subscriptions. QQ receive uses the intents allowed for that official Bot account; the default covers direct messages, group/C2C messages, and public guild mentions. QQ error 100016 is returned by the official token endpoint when the AppID and current ClientSecret do not match; regenerate the AppSecret and copy both values from the same bot application.
Polling cursors are persisted per account and the plugin ignores its own obntfy-* ntfy messages. Inbox deletion and delivery deduplication are intentionally separate. Removing an item from the visible inbox does not allow a provider retry with the same channel/message ID to run again. Incoming messages start consumer work in the background, so a long Cancip or model task does not block later receive cycles. One malformed or oversized attachment is rejected without stopping the rest of the provider batch. Realtime sockets remain active while Obsidian is runnable unless background receiving is disabled; mobile operating systems can still suspend the app, and the sockets reconnect with bounded backoff after Obsidian resumes.
Cancip can register its own handler without Ntfy Notifications importing Cancip:
const hub = app.plugins.plugins["android-ntfy-notifier"]?.api;
const unregister = hub?.registerIncomingHandler("cancip", async (message, context) => {
// Cancip decides whether to create a session, select a model, or reply.
return { accepted: true };
});
If Cancip loads before Ntfy Notifications, it can listen for the workspace event notification-hub:ready and register when the API becomes available. Every accepted message also emits notification-hub:incoming for lightweight observers; the configured incoming consumer remains the authoritative model/session handler.
The public API includes getStatus, getCapabilities, listChannels, send, schedule, simulate, test, receive, reply, inbox polling/retry/removal methods, scheduled-delivery listing/cancellation, reminder listing/add/update/removal/send/scan methods, dynamic Channel and incoming-consumer registration, and manager/settings open methods. External desktop agents can use the local obsidian://notification-hub URI templates shown by Copy setup in the settings. The URI supports send, schedule, simulate, test, receive, reply, and reminder; it is protected by a local token and a small request rate limit. The in-plugin API does not need that token.
Future notifications for providers without a remote scheduling API are retained in the bounded local outbound queue and delivered when due. Overlapping queue entries for the same notification are merged, cancelling one Channel leaves the other destinations intact, and a partial queued broadcast retains only the channels that did not confirm delivery.
Provider-backed sends require their normal receipt before success is reported: ntfy returns an ID; Telegram returns message_id; Feishu/Lark returns data.message_id; WeCom app returns msgid; Discord bot returns an ID; Slack bot returns ts; Matrix returns event_id; and QQ Bot returns a message ID. Generic webhooks, provider webhook modes, and Email gateways use successful HTTP completion because they do not share one portable receipt schema.
The settings show every active account as its own collapsible configuration block. Available channels stays collapsed by default and contains a real add flow for provider, stable account ID, and display name. The same provider can be added repeatedly, for example feishu:work, feishu:personal, email:work, and email:personal. In In use, enable/disable, make default, remove, and test are compact icon actions beside the account name. Removing an account keeps its credentials for later reuse. Each test icon sends through that exact account; simulate() remains available for a no-network, redacted request preview.
Email deliberately uses an HTTP gateway rather than embedding SMTP. This keeps the plugin small and compatible with Obsidian mobile; the gateway can be a provider API or a user's own relay.