binwiederhier/ntfy
53.0
Adequate · 6 August 2026
28.3k
lines of production code
Go
with JavaScript
4
measurements over time
What this system is
This system is a self-hosted notification service that manages message publishing, subscription, and delivery across multiple channels including web push, email, and SMS. It provides a robust backend with configurable storage backends (PostgreSQL, SQLite, S3) and supports fine-grained access control, abuse prevention, and structured logging. The platform also includes a modern web client with PWA capabilities and a comprehensive CLI tool for managing topics, users, and access permissions.
How it got here
2021 — multi-platform build and access control
11 changes.
This period focused on overhauling the build system to support multi-platform and multi-architecture Docker images, while introducing new features for abuse prevention and fine-grained topic access control. The release also added a Go client library, various utility helpers, and expanded example code for multiple languages and protocols.
2022–2025 — Web app modernization and infrastructure improvements
12 changes.
This period focused on modernizing the web application by migrating to Vite, introducing PWA and push notification support, and implementing a new account and messaging UI. Concurrently, the backend received significant infrastructure upgrades, including a structured logging system, an in-memory ACL cache for performance, and a new load generation tool for profiling.
2026 — PostgreSQL and S3 backend support
20 changes.
This period focused on expanding database and storage backends, introducing full support for PostgreSQL (including read replicas and schema migrations) and Amazon S3 for attachments. The work also included adding a message cache with database support, a migration tool for SQLite to PostgreSQL, and various structural improvements like action parsing and template context cancellation.
Features
Add 'ntfy access' CLI command for managing topic access control lists
Users can now manage access control lists (ACLs) for topics using the new 'ntfy access' command. This allows administrators to grant or revoke read, write, or deny permissions for specific users or anonymous clients ('everyone') on specific topics. The command supports listing current permissions, resetting access for specific users or topics, and integrates with the existing user management system to enforce fine-grained access control.
cmd · high confidence
Add PWA, service worker and Web Push support
The web application now supports Progressive Web App (PWA) functionality and Web Push notifications. A new service worker (sw.js) handles push messages, displays notifications, manages badge counts, and extends access tokens via periodic sync. A client-side configuration file (config.js) exposes feature flags (e.g., enable\_login, enable\_web\_push) and disallowed topics. This enables offline capabilities, background sync, and push notification delivery to the browser.
web/public · high confidence
Add PostgreSQL database driver implementation
Introduces a new PostgreSQL database driver in the db/pg package, providing connection pool management for both primary and read-replica database hosts. The implementation supports custom query parameters for pool configuration (e.g., pool\_max\_conns, pool\_max\_idle\_conns) and includes improved error handling for invalid database URLs and connection failures.
db/pg · high confidence
Add S3 backend for attachment storage
The attachment store now supports Amazon S3 as a storage backend, in addition to the existing file-system backend. Users can configure S3 storage via a URL containing access credentials, bucket name, and optional region/endpoint parameters. The implementation includes a new \backend\ interface, \backend\_s3.go\ for S3 operations, and \backend\_file.go\ for local file storage, allowing the system to store and retrieve attachment files in S3 buckets.
attachment · high confidence
Add S3 client implementation for object storage
The S3 client package is introduced, providing a Go implementation for interacting with S3-compatible object storage. It supports PutObject, GetObject, and ListObjectsV2 operations using AWS Signature V4 signing. The client handles both simple and multipart uploads (up to 5 GB in a single request, larger files use multipart upload), and includes logic to disable HTTP/2 via the disable\_http2=true URL parameter to work around compatibility issues with some providers. The implementation also includes comprehensive unit and integration tests for URL parsing, header signing, and error handling.
s3 · high confidence
Add Sprig template utility functions
The \util/sprig\ package is introduced, providing a comprehensive set of template functions for Go's \text/template\ and \html/template\ engines. This includes cryptographic hash functions (\sha1sum\, \sha256sum\, \sha512sum\, \adler32sum\), date and time formatting and manipulation (\date\, \dateModify\, \dateAgo\, \duration\), default value handling (\default\, \coalesce\, \empty\), dictionary and list operations (\dict\, \get\, \set\, \unset\, \pluck\, \keys\, \pick\, \omit\, \values\, \append\, \prepend\, \first\, \last\, \rest\, \chunk\), and string utilities (\trim\, \upper\, \lower\, \title\, \replace\, \indent\, \join\). The implementation includes corresponding test coverage for all new functions.
util/sprig · high confidence
Add configurable abuse ban-feed to automatically block abusive IPs
The server now supports an abuse ban-feed that automatically blocks IP addresses or prefixes after a configurable number of rejected requests. By default, the feature is disabled. When enabled via the \ban-file\ configuration option, the server tracks rejected requests (such as 4xx or 429 errors) and writes offending IPs to a log file. If the number of strikes within a \ban-window\ exceeds the \ban-threshold\, the offending IP is banned. This provides a lightweight mechanism to counter abuse and rate-limiting bypass attempts.
server · high confidence
Add fbsend tool for sending data messages to Firebase
A new Go-based utility, fbsend, has been added to the tools directory. It allows users to send data messages to Firebase topics via command-line arguments. The tool accepts a configuration file path and key-value pairs to include in the message payload.
tools/fbsend · high confidence
Add load generation tool for profiling
A new Go-based load generation tool has been added to the repository. This utility simulates 2,000 concurrent subscribers and 2,000 polling workers against a target server (defaulting to staging.ntfy.sh) to generate traffic for profiling purposes.
tools/loadgen · high confidence
Add s3cli tool for testing the S3 package
A new CLI tool named s3cli has been added to the codebase to facilitate testing of the s3 package. This tool supports uploading (put), downloading (get), deleting (rm), and listing (ls) objects in an S3-compatible storage system. It requires the S3\_URL environment variable for configuration and provides a simple interface for interacting with the underlying S3 client.
tools/s3cli · high confidence
Added PNG image resizing tool
A new shell script, tools/shrink-png.sh, has been added to automatically resize PNG images. When run with one or more PNG file paths, the script checks each image's height; if it exceeds 1200 pixels, the image is resized down to that maximum height using ImageMagick's convert command. Images already within the height limit are skipped.
tools · high confidence
Added ntfy client systemd service
A new systemd service file (ntfy-client.service) has been added to the client/user directory, configuring the ntfy client to subscribe using a configuration file located at %h/.config/ntfy/client.yml. This enables the ntfy client to run as a background service that automatically starts after the network is available and restarts on failure.
client/user · high confidence
Added ntfy load testing tool
A new Go-based load testing utility has been added to simulate production traffic patterns for the ntfy staging server. The tool replicates real-world request distributions (polling, publishing, streaming) and allows users to configure parameters like request-per-second rate, scale factor, and concurrent stream counts to stress-test the server.
tools/loadtest · high confidence
Added pgimport tool for migrating ntfy data from SQLite to PostgreSQL
A new one-off migration script, pgimport, has been added to the tools/pgimport directory. This tool allows users to import data from existing SQLite databases (message cache, user auth, and web push stores) into a PostgreSQL database. The script supports automatic schema creation, validates specific schema versions to ensure compatibility, and includes verification of row counts and content after import. It is designed to be idempotent and safe to re-run, with invalid UTF-8 characters in messages being replaced with the Unicode replacement character.
tools/pgimport · high confidence
Introduce client library and configuration for self-hosted ntfy usage
Adds a new Go client library and configuration system that allows users to manage default hosts, authentication credentials (user/password or token), and subscription actions via a YAML config file (client.yml). This enables the CLI to automatically apply these settings for publish and subscribe commands, supporting features like default commands for notifications and filtering messages by priority or tags.
client · high confidence
Introduce new web app components for account, messaging, and navigation
The web application now features a suite of new React components including Account, ActionBar, App, AttachmentIcon, AvatarBox, DialogFooter, EmailVerify, EmojiPicker, ErrorBoundary, Login, MarkdownContent, Messaging, Navigation, and Notifications. These components collectively provide the core user interface for the web app, enabling users to manage their account, send and receive messages, navigate the application, view notifications, and handle errors gracefully.
web/src/components · high confidence
Introduce schema migration framework for database versioning
Added a new \db/schema\ package that provides a unified framework for managing database schema versions and migrations. The \Migrate\ function handles creating or upgrading a store's schema in a single transaction, supporting both PostgreSQL and SQLite backends. For PostgreSQL, it uses an advisory lock to prevent race conditions during cold-boot schema creation. The framework supports versioned migration steps, allowing the application to upgrade the database schema incrementally as the codebase evolves.
db/schema · high confidence
Introduce web app account, API, and connection management modules
The web application now includes dedicated modules for managing user accounts, API interactions, and WebSocket connections. A new \AccountApi\ class handles account lifecycle operations including login, logout, account creation, password changes, and access token management. The \Api\ module provides methods for polling topics, publishing messages, checking topic authentication, and managing web push subscriptions. Additionally, \Connection\ and \ConnectionManager\ classes manage WebSocket connections with automatic reconnection and backoff, while the \Notifier\ class handles desktop notifications and sound playback. These modules work together to provide a robust client-side experience for managing subscriptions, notifications, and user sessions.
web/src/app · high confidence
Message cache now supports PostgreSQL and SQLite backends
The message cache implementation has been updated to support both PostgreSQL and SQLite databases. This change introduces database-specific query definitions and schema management for both backends, allowing the system to store and retrieve messages from either a PostgreSQL or SQLite database. The implementation includes schema migrations for both databases, ensuring that existing databases are properly upgraded to the latest schema version. Tests have been added to verify the migration process and functionality for both SQLite and PostgreSQL backends.
message · high confidence
Migrate web app build to Vite and add PWA support
The web application's build system has been migrated from the previous setup to Vite, introducing a modern, faster development and build pipeline. This change includes the addition of PWA (Progressive Web App) support via the Vite PWA plugin, enabling features like service worker registration and offline capabilities. Additionally, the project now uses ESLint with the Airbnb config and Prettier for code quality and formatting, with specific configurations for testing via Vitest.
web · high confidence
New email formatting and sending implementation
The mail package now includes a new \format.go\ file that handles formatting notification emails, including support for emoji tags, priority levels, and UTF-8 subject encoding. A corresponding \format\_test.go\ file adds comprehensive tests for these formatting scenarios. The \sender.go\ file introduces a \Sender\ interface and \realSender\ struct to handle sending notification emails, email verification links, and password reset emails via SMTP, tracking success/failure counts.
mail · high confidence
New examples for subscribing and publishing via various languages and protocols
Added new example code for subscribing to and publishing messages using Go, PHP, Python, and Bash/Shell scripts, alongside HTML examples for WebSocket and Server-Sent Events (SSE) in the browser. A new Grafana dashboard configuration for monitoring ntfy metrics was also added.
examples · high confidence
New per-prefix abuse ban system
The ban package now implements a weighted strike system that tracks per-IP-prefix (not per-visitor) rejection counts. When a prefix accumulates enough weighted strikes within a rolling window, the service appends a ban line to a file that fail2ban tails, effectively banning the entire /32 or /64 prefix. The system supports configurable weights for specific error codes or HTTP status families, and includes background loops for periodic pruning of idle prefixes and throttled file writes.
ban · high confidence
New utility helpers for batching, rate limiting, and content-type sniffing
Added several new utility components to the \util\ package: a generic \BatchingQueue\ for collecting and emitting batches of elements based on size or timeout; a \ContentTypeWriter\ that automatically detects and sets the \Content-Type\ header while preventing HTML from being served as such; a \CachingEmbedFS\ wrapper for embedded static files that supports HTTP 304 caching; a \Gzip\ middleware for transparent response compression; and a \Limiter\ interface with \FixedLimiter\ and \RateLimiter\ implementations for tracking and enforcing limits on writes and reads. These additions provide reusable building blocks for message batching, file serving, and rate limiting.
util · high confidence
Repository structure and build system overhaul
The project has been restructured with a new Makefile, GoReleaser configuration, and Dockerfiles to support multi-platform builds (Linux, Windows, macOS) and multi-architecture Docker images (amd64, arm64, armv7, armv6). This includes adding a .gitignore, .git-blame-ignore-revs, .go-version, and .gitpod.yml to streamline development and CI/CD workflows.
(repo-wide) · high confidence
Service worker registration and periodic update logic
The web application now registers a service worker via a new \registerSW\ module, which handles the initial registration and sets up a periodic check (every hour) to fetch and update the service worker script, ensuring the app can receive updates while running.
web/src · high confidence
Support for read-only Postgres replicas with automatic health checking
The database layer now supports connecting to read-only Postgres replicas in addition to the primary database. The new \DB\ type manages a primary connection and an optional list of replica connections. Read-only queries can be routed to healthy replicas using a round-robin strategy, with automatic periodic health checks to detect and log unhealthy replicas. Write operations continue to use the primary database. This change enables read scaling and improved availability for read-heavy workloads.
db · high confidence
Web push subscription storage now supports PostgreSQL
The web push feature now persists subscription data in a database, with initial implementations for both SQLite and PostgreSQL backends. This change introduces a new \webpush\ package containing a \Store\ interface and concrete implementations (\store\_postgres.go\, \store\_sqlite.go\) that manage subscription records, including upserts, topic associations, and expiration handling. The PostgreSQL implementation includes a schema migration to create the \webpush\_subscription\ and \webpush\_subscription\_topic\ tables, while the SQLite implementation creates equivalent tables. This provides a persistent, queryable store for web push subscriptions, replacing any previous in-memory or non-persistent storage.
webpush · high confidence
Behavioural changes
1 commit (0 fixes) modifying assets
A change to existing behaviour in assets — 1 commit, 1 file.
assets · low confidence · unverified
Extracted action button parsing into a dedicated action package
The logic for parsing notification action buttons has been moved into a new \action\ package. This refactoring isolates the parsing of both JSON and simple text formats for action buttons, which are used to define interactive elements in notifications. The change improves code organization by separating the parsing logic from the main application code.
action · high confidence
Improved package installation and upgrade handling
The Debian package scripts now ensure the 'ntfy' user and group exist, set correct permissions on cache files, and properly restart the systemd service during upgrades. The pre-install script also migrates the old config file to the new name.
scripts · medium confidence
Introduces a structured Message model with UTF-8 sanitization and action support
The model package now defines a comprehensive Message struct that includes fields for events, attachments, and user-defined actions. A key behavioral change is the addition of a SanitizeUTF8 method that cleans invalid UTF-8 sequences and strips NUL bytes from all user-supplied string fields, ensuring downstream consumers receive clean data. The model also supports structured actions with headers, methods, and bodies, and provides factory methods for creating various message types.
model · high confidence
Introduces an in-memory access control list (ACL) cache to speed up topic permission checks
The user package now maintains an in-memory cache of user access permissions, significantly reducing database load for common read and write checks. The cache is automatically refreshed after ACL mutations and periodically in the background, with support for both full and per-user reloads. This change is accompanied by a database schema migration (upgrading to schema version 9) and new SQL queries for both PostgreSQL and SQLite backends to support the cache.
user · high confidence
Metrics implementation moved to dedicated package
The Prometheus metrics for the ntfy server have been moved into a new \metrics\ package. This change decouples the metrics definitions from the server package, allowing call sites to update metrics without depending on the server package. The metrics are registered with the default Prometheus registry on import, ensuring they are exposed if the /metrics handler is mounted. A corresponding test ensures all expected metric names are registered and that collectors are never nil.
metrics · high confidence
Moved Twilio client implementation to the twilio package
The Twilio client implementation, including the client logic, types, and tests, has been moved into the twilio package. This change reorganizes the codebase by moving the Twilio-related code from the root directory into a dedicated twilio/ directory, improving code organization and separation of concerns for the Twilio integration.
twilio · high confidence
New structured logging system with JSON support and field-level log level overrides
The application now uses a new structured logging package in the \log\ directory, replacing the previous implementation. This change enables JSON-formatted log output, supports multiple log level overrides for specific fields (e.g., enabling debug logs for specific tags or context values), and provides a fluent API for adding context and custom fields to log events. Users will see structured, machine-readable logs by default or when configured, with more granular control over which log levels are active for specific data points.
log · high confidence
Payments module now supports build-time disabling of Stripe integration
The payments package now provides two implementations based on the 'nopayments' build tag. When the 'nopayments' tag is absent, the module integrates with Stripe (v74) to handle subscriptions and pricing, exposing types like SubscriptionStatus and PriceRecurringInterval. When the 'nopayments' tag is present, the module provides dummy implementations that disable Stripe support, allowing users to build the application without the Stripe dependency. This change enables users to exclude payment processing features entirely during the build process.
payments · high confidence
Template execution now supports context cancellation to prevent CPU denial-of-service
The vendored \text/template\ package in \template/gotext/\ has been patched to add \ExecuteContext\ and \ExecuteTemplateContext\ methods, which accept a \context.Context\ to allow aborting template execution when the context is canceled or its deadline passes. This prevents user-supplied message templates from causing CPU denial-of-service by running indefinitely. The \server/server\_template.go\ layer wraps execution in a timeout and maps \context.DeadlineExceeded\ to a 400 error, ensuring that long-running or tight-loop templates are interrupted promptly.
template · high confidence
Updated link styling and embedded Roboto font support
The application now applies custom styling to links, setting the default and visited link color to \#338585 and the hover color to \#317f6f with no underline. Additionally, the CSS now includes embedded font-face definitions for the Roboto typeface (weights 300, 400, 500, and 700) to ensure consistent typography across browsers.
web/public/static/css · high confidence
Test coverage
Added database schema comparison helpers for tests; Added test environment setup for web components; Added test helper utilities for server lifecycle management.
Dependencies
Updated project dependencies and build configuration
The project's dependency management has been updated. The Go module has been upgraded to version 1.25.8 and the module path changed to \heckel.io/ntfy/v2\. Several Go packages were updated, including \github.com/emersion/go-smtp\ (pinned to v0.17.0), \github.com/gorilla/websocket\, and \firebase.google.com/go/v4\. For the web application, the \package.json\ and \package-lock.json\ were added, introducing dependencies such as React, Vite, MUI, and Dexie, along with development tools like ESLint and Prettier.
(dependencies) · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Score
- CAI 50 → 53 (+2.9)
- Rubric changed (rubric-2026.08.17 → rubric-2026.08.19) — scores are not directly comparable.
Lenses
- Code Health 44 → 46 (+2.3)
- Architecture 92 → 96 (+4.2)
- Maturity 55 → 61 (+6.6)
- Readiness 49 → 49 (+0.6)
- Security 75 → 83 (+8.1)
- Accessibility 53 → 63 (+9.9)
Resolved (87)
- Change coupling: Account.jsx ↔ ActionBar.jsx (web/src/components/Account.jsx)
- Change coupling: Account.jsx ↔ Login.jsx (web/src/components/Account.jsx)
- Change coupling: Login.jsx ↔ Signup.jsx (web/src/components/Login.jsx)
- Change coupling: Notifications.jsx ↔ UpgradeDialog.jsx (web/src/components/Notifications.jsx)
- Change coupling: Preferences.jsx ↔ UpgradeDialog.jsx (web/src/components/Preferences.jsx)
- Change coupling: PublishDialog.jsx ↔ UpgradeDialog.jsx (web/src/components/PublishDialog.jsx)
- Change coupling: SubscribeDialog.jsx ↔ UpgradeDialog.jsx (web/src/components/SubscribeDialog.jsx)
- Change coupling: SubscriptionPopup.jsx ↔ UpgradeDialog.jsx (web/src/components/SubscriptionPopup.jsx)
- Dependency hygiene not measured — no supported dependency manifest was read
- Duplicated block (10 lines × 2) (message/cache_sqlite_schema.go)
- Duplicated block (10 lines × 2) (server/server_manager.go)
- Duplicated block (10 lines × 2) (user/manager.go)
- Duplicated block (10 lines × 2) (util/sprig/list.go)
- Duplicated block (11 lines × 2) (cmd/publish.go)
- Duplicated block (11 lines × 2) (server/server.go)
- Duplicated block (11 lines × 2) (server/server_account.go)
- Duplicated block (11 lines × 2) (tools/loadtest/main.go)
- Duplicated block (11 lines × 2) (tools/pgimport/main.go)
- Duplicated block (11 lines × 2) (util/sprig/list.go)
- Duplicated block (11 lines × 3) (cmd/serve.go)
- …and 67 more
New (44)
- Change coupling: Connection.js ↔ App.jsx (web/src/app/Connection.js)
- Change coupling: ConnectionManager.js ↔ App.jsx (web/src/app/ConnectionManager.js)
- Change coupling: config.go ↔ server_twilio.go (server/config.go)
- Change coupling: config.go ↔ time.go (server/config.go)
- Change coupling: server.go ↔ config.js (server/server.go)
- Change coupling: tier.go ↔ config.go (cmd/tier.go)
- Dependency hygiene not measured — dependency manifest found but not parsed for hygiene
- Duplicated block (10 lines × 2) (cmd/publish.go)
- Duplicated block (10 lines × 2) (server/server_account.go)
- Duplicated block (10 lines × 2) (tools/pgimport/main.go)
- Duplicated block (10 lines × 4) (cmd/user.go)
- Duplicated block (11 lines × 2) (message/cache.go)
- Duplicated block (11 lines × 2) (server/server_payments.go)
- Duplicated block (12 lines × 2) (user/manager.go)
- Duplicated block (14 lines × 2) (tools/pgimport/main.go)
- Duplicated block (7 lines × 3) (util/sprig/list.go)
- Duplicated block (8 lines × 2) (server/server_account.go)
- Duplicated block (8 lines × 2) (server/server_payments.go)
- Duplicated block (9 lines × 2) (server/server.go)
- Duplicated block (9 lines × 2) (user/manager.go)
- …and 24 more
Changes since last survey
- 42 commits — 40 feature/other, 2 fixes
By area
- (repo) — 12 commits
- web/public — 6 commits
- (root) — 4 commits
- db/schema — 4 commits
- docs/config.md — 4 commits
- .github/workflows — 2 commits
- db/test — 1 commit
- docs/install.md — 1 commit
- docs/integrations.md — 1 commit
- docs/releases.md — 1 commit
- message/cache_postgres_test.go — 1 commit
- server/server.go — 1 commit
- user/manager.go — 1 commit
- user/manager_sqlite_schema.go — 1 commit
- util/sprig — 1 commit
- web/package-lock.json — 1 commit
Notable commits
- fix: Merge pull request #1885 from binwiederhier/revert-1882-main
- fix: Revert "add tzdata to docker arm build"
- change: Ban-feed
- change: Bump docker/login-action in the all group across 1 directory
- change: Bump fast-uri
- change: Bump install notes
- change: Bump release notes
- change: Combine message dispatching into a dispatch function
- change: Comment
- change: Derp
- change: Do not include secrets in the config hash
- change: Harden migration
- change: Limit memory usage in templates
- change: Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web
- change: Merge branch 'release-2.26.x'
- change: Merge pull request #1820 from houllette/add-ex-ntfy-library
- change: Merge pull request #1847 from nexus-uw/patch-2
- change: Merge pull request #1868 from binwiederhier/schema-migration
- change: Merge pull request #1871 from binwiederhier/message-cache-migration2
- change: Merge pull request #1873 from binwiederhier/user-schema-migration
- …and 22 more
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
binwiederhier/ntfy was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 6 August 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit 4c2b69e0591b51d7ed7b2e71954f0f7be936b47f — the exact code this score is about.
- Scored under rubric-2026.08.19 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer latest.