I’ve always been fascinated by how a simple ping or pop-up can transform the way I interact with my applications.
That’s because real-time alerts drive faster responses and better visibility into automation workflows—they let me know the moment a long-running script finishes, an error pops up, or an important event happens in one of my services.
In this article, I’ll walk you through everything you need to get started with ntfy.sh and Python:
- What notifications really are, and why they’ve become such an integral part of modern apps.
- An introduction to ntfy.sh, the lightweight, open-source notification service I’ve come to rely on.
- Key use cases where I’ve used ntfy.sh—from DevOps monitoring to home automation.
- A hands-on sample application, complete with Python code you can run today to send notifications to your phone and desktop browser.
By the end, you’ll have a working notification system powered by Python and ntfy.sh, ready to alert you wherever you go.
You can get the source code for this script at: https://github.com/nunombispo/Folder-Monitor-with-ntfy.sh
This book offers an in-depth exploration of Python's magic methods, examining the mechanics and applications that make these features essential to Python's design.
What Are Notifications?
I remember the first time I received a notification that wasn’t just an email or SMS—it popped up on my screen without me asking, and it felt like magic.
At its core, a push notification is exactly that: an alert “pushed” from a server directly to my device, even when my app isn’t actively requesting updates.
This means I don’t have to poll a service or refresh a page; instead, the server reaches out and says, “Hey, something happened!”
Over the years I’ve come to appreciate that notifications actually come in a few flavors.
Local notifications are scheduled by the app on the device itself—think reminders you set in a to-do app.
Remote notifications, on the other hand, are triggered by an external server (the classic push notification scenario).
And then you can split those further by platform: mobile notifications on iOS or Android, and web notifications delivered straight to my browser on desktop or mobile.
I’ve used notifications for so many things in my projects:
- News flashes: Instantly alerting me when a breaking news keyword shows up in an API feed.
- Reminders: Pinging me to stand up during long coding sessions or to review my daily task list.
- Error alerts: Letting me know the moment a critical server job fails so I can jump in and fix it.
- Marketing: Sending targeted promotions or updates to users when they opt in.
What Is ntfy.sh?
I first stumbled upon ntfy.sh when I needed a no-frills way to send push alerts from my scripts.
ntfy.sh is a simple HTTP-based pub-sub notification service that’s completely free and open source.
With nothing more than a PUT or POST request, I can send a message from any computer and have it pop up on my phone or desktop—no sign-up, no fees, and full source code if I ever want to self-host.
How it works
What I love about ntfy.sh is its effortless setup: topics are created on the fly the moment I publish or subscribe to them.
Behind the scenes, ntfy.sh treats the topic name itself as a lightweight “password”—so I simply choose a unique topic (for example, mytopic
), send messages with curl -d "Hello" ntfy.sh/mytopic
, and open the ntfy app or web UI to subscribe to that topic.
Messages flow instantly from my script to my devices via HTTP PUT/POST, or through the ntfy CLI if I prefer.
Key features
Over time, I’ve come to rely on several powerful features that ntfy.sh supports out of the box:
- Priorities: I can set
Priority: high
in the headers to differentiate urgencies. - Tags & Action Buttons: I tag messages and even include quick-action buttons for one-tap responses.
- Attachments: Sending images or files is as simple as adding an
X-Image
orX-Attachment
header. - Markdown support: In the web UI, I can include formatted text, links, and emojis to make notifications richer.
With this minimal setup, ntfy.sh lets me focus on my automation logic rather than wrestling with complex notification infrastructure.

Mug Trust Me Prompt Engineer Sarcastic Design
A sarcastic "prompt badge" design coffee mug, featuring GPT-style neural network lines and a sunglasses emoji.
Perfect for professionals with a sense of humor, this mug adds a touch of personality to your morning routine.
Ideal for engineers, tech enthusiasts, and anyone who appreciates a good joke.
Great for gifting on birthdays, holidays, and work anniversaries.
Use Cases for ntfy.sh
I’ve integrated ntfy.sh into so many parts of my workflow—here are a few examples:
System monitoring & DevOps: Whenever one of my CI/CD jobs fails or a deployment completes, I have a tiny Python script that POSTs an alert to my dev-ops
topic. The moment something goes wrong (or right!), ntfy.sh pushes a notification to my phone and browser so I can react instantly—no polling or dashboard refreshing required.
Home automation: I hooked up my temperature sensors to send messages via ntfy.sh. Now, if my attic temperature spikes, I get an instant push alert—perfect for keeping an eye on my home lab from anywhere.
Web scraping & data pipelines: In one of my Python ETL scripts, I wrap key steps in try/except blocks that POST errors or threshold-breach messages to an etl-alerts
topic. If my data pipeline ever hiccups—like a missing field or unexpected data spike—I’m notified right away to dive in and fix it.
Personal productivity: I even use ntfy.sh for reminders—sending messages to a reminders
topic whenever it’s time to stand up, review my task list, or prep for my next meeting. It’s like having a custom, lightweight to-do app that pops up wherever I am.
Sample Application: Folder Monitor with ntfy.sh
For one of my projects, I needed a way to keep an eye on a critical folder—whether files were being added, modified, moved, or deleted—without constantly refreshing a file browser.
That’s when I wrote a Folder Monitor in Python that sends real-time push notifications via ntfy.sh.
If you want a more in-depth view on file system monitoring with Python, check out my other article: https://developer-service.blog/mastering-file-system-monitoring-with-watchdog-in-python/
Here’s how this sample application it works, with the key code snippets for each part.
Setting up the file watcher
I leverage the watchdog
library’s Observer
and FileSystemEventHandler
to watch a directory of my choice.
Don't forget to install:
pip install requests watchdog
In my main()
function, I parse command-line arguments for the folder path, ntfy topic, file extensions filter, and whether to recurse into subdirectories.
Once validated, I instantiate my custom FileChangeHandler
and start the observer loop:
import json
import time
import argparse
import logging
import os
import sys
from datetime import datetime
import requests
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# Set up logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
def main():
parser = argparse.ArgumentParser(description="Monitor a folder and send ntfy.sh notifications on file changes")
parser.add_argument("--path", required=True, help="Path to the folder to monitor")
parser.add_argument("--topic", required=True, help="ntfy.sh topic for notifications")
parser.add_argument("--extensions", help="Comma-separated list of file extensions to monitor (e.g., .txt,.pdf,.docx)")
parser.add_argument("--include-directories", action="store_true", help="Include directory events in notifications")
parser.add_argument("--recursive", action="store_true", help="Watch subdirectories recursively")
args = parser.parse_args()
# Check if the path exists
if not os.path.exists(args.path):
logger.error(f"The specified path does not exist: {args.path}")
sys.exit(1)
# Process file extensions if provided
include_extensions = None
if args.extensions:
include_extensions = [ext.strip().lower() if ext.strip().startswith('.') else f'.{ext.strip().lower()}'
for ext in args.extensions.split(',')]
# Create the event handler and observer
event_handler = FileChangeHandler(
args.topic,
include_extensions=include_extensions,
exclude_directories=not args.include_directories
)
observer = Observer()
observer.schedule(event_handler, args.path, recursive=args.recursive)
observer.start()
logger.info(f"Monitoring folder: {os.path.abspath(args.path)} (recursive: {args.recursive})")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
logger.info("Monitoring stopped by user")
# Send notification that monitoring has stopped
event_handler.send_notification(
"Folder monitoring stopped by user",
title="Monitoring Stopped",
priority=3, # default priority
tags="stop_sign"
)
observer.stop()
observer.join()
if __name__ == "__main__":
main()
If I hit Ctrl+C
, the script catches the KeyboardInterrupt
, stops the observer, notifies me that monitoring has stopped, and then exits cleanly.
Initializing notifications
As soon as the handler is created, I immediately send a “monitoring started” message to my ntfy.sh topic—so I know the service is up and running.
This happens right inside the FileChangeHandler.__init__()
:
class FileChangeHandler(FileSystemEventHandler):
"""Handle file system events and send notifications"""
def __init__(self, topic, include_extensions=None, exclude_directories=True):
"""
Initialize the handler
Args:
topic (str): ntfy topic to send notifications to
include_extensions (list, optional): List of file extensions to monitor (e.g. ['.txt', '.pdf'])
exclude_directories (bool): Whether to exclude directory events
"""
self.topic = topic
self.include_extensions = include_extensions
self.exclude_directories = exclude_directories
# Send a notification that monitoring has started
self.send_notification(
"Started monitoring folder for changes",
title="Folder Monitoring Started",
priority=3, # default priority
tags="rocket"
)
logger.info(f"Started monitoring. Notifications will be sent to topic: {topic}")
if include_extensions:
logger.info(f"Monitoring only these extensions: {', '.join(include_extensions)}")
Filtering events
Inside FileChangeHandler
, the should_process_event()
method ensures I only send notifications for events I care about.
I can exclude directory-only events or limit notifications to specific file extensions simply by passing flags when I launch the script:
This article is for subscribers only
To continue reading this article, just register your email and we will send you access.
Subscribe NowAlready have an account? Sign In