Skip to content
CAI
Software that uses CAICheck a score

binwiederhier/ntfy

53.0

Adequate · 6 August 2026

28.3k

lines of production code

Go

with JavaScript

4

measurements over time

CAI band scale
CAI trend line
CAI lens gauges

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

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.