RepoPilotOpen in app →

binwiederhier/ntfy

Send push notifications to your phone or desktop using PUT/POST

Healthy

Healthy across the board

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit today
  • 11 active contributors
  • Apache-2.0 licensed
Show 3 more →
  • CI configured
  • Tests present
  • Concentrated ownership — top contributor handles 76% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/binwiederhier/ntfy)](https://repopilot.app/r/binwiederhier/ntfy)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/binwiederhier/ntfy on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: binwiederhier/ntfy

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/binwiederhier/ntfy shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across the board

  • Last commit today
  • 11 active contributors
  • Apache-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Concentrated ownership — top contributor handles 76% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live binwiederhier/ntfy repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/binwiederhier/ntfy.

What it runs against: a local clone of binwiederhier/ntfy — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in binwiederhier/ntfy | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch main exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 30 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>binwiederhier/ntfy</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of binwiederhier/ntfy. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/binwiederhier/ntfy.git
#   cd ntfy
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of binwiederhier/ntfy and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "binwiederhier/ntfy(\\.git)?\\b" \\
  && ok "origin remote is binwiederhier/ntfy" \\
  || miss "origin remote is not binwiederhier/ntfy (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
  && ok "default branch main exists" \\
  || miss "default branch main no longer exists"

# 4. Critical files exist
test -f "cmd/serve.go" \\
  && ok "cmd/serve.go" \\
  || miss "missing critical file: cmd/serve.go"
test -f "db/db.go" \\
  && ok "db/db.go" \\
  || miss "missing critical file: db/db.go"
test -f "cmd/app.go" \\
  && ok "cmd/app.go" \\
  || miss "missing critical file: cmd/app.go"
test -f "attachment/store.go" \\
  && ok "attachment/store.go" \\
  || miss "missing critical file: attachment/store.go"
test -f "client/client.go" \\
  && ok "client/client.go" \\
  || miss "missing critical file: client/client.go"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 30 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~0d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/binwiederhier/ntfy"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

ntfy is a lightweight HTTP-based pub-sub notification service written in Go that lets you send push notifications to phones and desktops via simple PUT/POST requests without authentication or signup. It runs as a single binary (with optional Firebase, SMTP, and S3 backends for attachments) and powers the free ntfy.sh service alongside open-source Android and iOS client apps, making it ideal for CI/CD pipelines, server alerts, and personal automation. Modular monorepo structure: cmd/ contains CLI entry points, server/ (inferred from file references) handles HTTP routing and pub-sub logic, attachment/ handles file storage with pluggable backends (file, S3), client/ is the standalone CLI client with OS-specific config (config_darwin.go, config_unix.go, config_windows.go), and web/ is the React frontend. Go is the main backend (~1.6M LOC), JavaScript/React frontend (~650K LOC).

👥Who it's for

System administrators, DevOps engineers, and automation enthusiasts who need to send notifications from scripts and cron jobs to their phones; also self-hosters who want to run their own notification infrastructure without managing complex push services or authentication systems.

🌱Maturity & risk

Actively developed and production-ready. The project has structured CI/CD (GitHub Actions workflows for test, build, docs, release), comprehensive test coverage, tagged releases with goreleaser, and a healthy community (Discord/Matrix linked in README). Recent commit activity visible in workflows and the module is at v2 with Go 1.25 support, indicating active maintenance beyond proof-of-concept stage.

Moderate complexity with multiple optional backends (Firebase, PostgreSQL, S3, SMTP email) that add dependency surface area (stripe-go, google cloud libraries, Firebase SDK). Single-maintainer visibility (binwiederhier as primary), though community-supported. No major red flags visible, but production deployments should test their specific backend configuration (Firebase, database, SMTP) carefully as failures cascade from core notification logic.

Active areas of work

Active development with security updates (SECURITY.md present, .git-blame-ignore-revs suggests cleanup work), recent Dockerfile optimizations (Dockerfile-arm for ARM builds), workflow enhancements (.github/workflows/ shows test, build, docs, release automation), and likely ongoing client app sync across Android/iOS platforms based on README references to maintained app repos.

🚀Get running

Clone and build locally: git clone https://github.com/binwiederhier/ntfy.git && cd ntfy && make build (Makefile present). For development with live reload and hot-patching, check Makefile targets. For Docker deployment: docker build -t ntfy . && docker run -p 80:80 ntfy serve (Dockerfile provided). The .gitpod.yml indicates one-click cloud development is supported.

Daily commands: Backend server: make run or ./ntfy serve (see Makefile for targets). Frontend dev: Navigate to web/ and likely npm install && npm start (standard JS project layout expected). Docker: docker-compose up if compose file exists, else docker build -f Dockerfile -t ntfy . && docker run -p 80:80 -p 8080:8080 ntfy. For client CLI: ./ntfy after build, or see client/client.go for client-mode usage.

🗺️Map of the codebase

  • cmd/serve.go — Main server entry point that orchestrates HTTP routing, WebSocket handling, and core notification delivery logic—essential for understanding request flow.
  • db/db.go — Database abstraction layer defining the core persistence interface used across all data operations; understanding this is mandatory for schema and query changes.
  • cmd/app.go — Application configuration and initialization; contains the central App struct that wires together all subsystems (server, database, attachments, auth).
  • attachment/store.go — Attachment storage abstraction managing file uploads/downloads; critical for understanding multi-backend (local, S3) support.
  • client/client.go — CLI client implementation that demonstrates the ntfy protocol and message subscription patterns used by end users.
  • cmd/publish.go — Publish/POST command handler; core business logic for receiving and routing notifications to subscribers.
  • cmd/subscribe.go — Subscribe/GET and WebSocket handler; implements the notification delivery mechanism to clients via streaming or WebSocket.

🛠️How to make changes

Add a new HTTP endpoint

  1. Define the request handler function in cmd/serve.go or a new cmd/<feature>.go file following the pattern of publish.go and subscribe.go (cmd/serve.go)
  2. Register the route in the setupRoutes() method within cmd/serve.go, mapping HTTP method + path to your handler (cmd/serve.go)
  3. If the endpoint requires database access, add methods to db/db.go (interface) and implement them in db/pg/pg.go (db/db.go)
  4. Write integration tests in cmd/serve_test.go following the existing test patterns with test servers and assertions (cmd/serve_test.go)

Add a new database table or schema change

  1. Add new data model struct(s) to db/types.go (db/types.go)
  2. Define new query methods in the Database interface in db/db.go (db/db.go)
  3. Implement the interface methods in db/pg/pg.go with SQL queries and schema migrations (db/pg/pg.go)
  4. Add tests in db/pg/pg_test.go to verify schema and query logic (db/pg/pg_test.go)

Add support for a new attachment backend

  1. Create a new backend implementation file (attachment/backend_<provider>.go) implementing the Backend interface from attachment/backend.go (attachment/backend.go)
  2. Register the backend factory in attachment/store.go in the NewAttachmentStore() method (attachment/store.go)
  3. Add configuration options to cmd/config_loader.go for the new backend (connection strings, credentials, etc.) (cmd/config_loader.go)
  4. Write backend-specific tests in attachment/store_<provider>_test.go following the patterns in existing backend test files (attachment/store_test.go)

Add a new CLI command for the client

  1. Create a new command file in cmd/<command>.go (follow patterns in cmd/publish.go, cmd/subscribe.go) with a handler function (cmd/publish.go)
  2. Register the command in cmd/app.go in the RegisterCommands() method with urfave/cli/v2 registration (cmd/app.go)
  3. Add unit tests in cmd/<command>_test.go to cover command logic, argument parsing, and edge cases (cmd/publish_test.go)

🔧Why these technologies

  • Go (1.25) — Lightweight, compiled binaries; excellent concurrency with goroutines; ideal for a stateless, horizontally-scalable notification server; cross-platform compilation for ARM/x86.
  • SQLite (default) + PostgreSQL (optional) — SQLite for easy self-hosted single-instance deployments; PostgreSQL option for multi-instance clustered deployments with shared state.
  • WebSocket (gorilla/websocket) — Bidirectional persistent connections for real-time push notifications without polling overhead; native browser support.
  • S3-compatible storage — Abstracts attachment persistence across local filesystem, AWS S3, MinIO, and other compatible backends without coupling to a single vendor.
  • YAML configuration — Human-readable server config matching industry standards (Kubernetes, Prometheus, etc.); enables declarative deployment via Helm/Docker Compose.

⚖️Trade-offs already made

  • Stateless HTTP + shared database vs. in-memory cluster

    • Why: Allows horizontal scaling and cloud-native deployments; trades per-instance state caching for database consistency.
    • Consequence: Database becomes the bottleneck; requires careful query optimization and indexing; connection pooling is critical.
  • Attachment storage abstraction (pluggable backends)

    • Why: Supports multiple deployment scenarios (self-hosted local storage, cloud S3, enterprise MinIO) from single codebase.
    • Consequence: Added complexity in store initialization and backend selection; tests must cover multiple backends.
  • No built-in rate-limiting or quotas at API level (delegated to reverse proxy/config)

    • Why: Simplifies core server; allows operators to choose rate-limiting strategy (nginx, traefik, API gateway) per deployment.
    • Consequence: Operators must manually configure protections; default deployment is vulnerable to abuse without external controls.
  • Single-topic pub/sub model (no message routing/filtering)

    • Why: Simplicity; all subscribers to a topic receive all messages; avoids complex subscription DSLs.
    • Consequence: Clients must filter/deduplicate on their side; not suitable for high-cardinality topic trees.

🚫Non-goals (don't propose these)

  • Does not provide message persistence beyond the database retention window (not a full message log service like Kafka).
  • Does not implement built-in load balancing or horizontal scaling orchestration (assumes external load balancer).
  • Does not enforce encryption in transit at the application layer (relies on reverse proxy TLS termination).
  • Does not provide topic-level access control beyond simple auth tokens (authorization is per-instance or per-user, not granular per-topic).
  • Does not support distributed consensus/raft-based replication (not a distributed system; requires external DB for HA).
  • Mobile push notifications are sent via third-party services (APNS, FCM), not natively; ntfy acts as a bridge.
  • Does not implement MQTT, AMQP, or other standard message queue protocols (HTTP/WebSocket only).

🪤Traps & gotchas

Database choice matters: SQLite is default but stateless (each restart resets), production requires FIRESTORE_PROJECT or POSTGRES_* env vars to persist topics—check attachment/store_test.go and server initialization for config. Firebase setup non-obvious: requires FIRESTORE_PROJECT env var AND valid Google credentials in GOOGLE_APPLICATION_CREDENTIALS path. SMTP relay: emersion/go-smtp v0.17.0 pinned due to v0.18 breaking changes (see go.mod replace directive)—upgrading may break email notifications. S3 backend: requires bucket policy and credentials, falls back to file storage silently if misconfigured. Build requires cgo: mattn/go-sqlite3 needs C compiler; Docker build handles this but cross-compilation can fail. Web frontend: built as static assets and embedded into binary (check Makefile web build target), changes require rebuild.

🏗️Architecture

💡Concepts to learn

  • binwiederhier/ntfy-android — Official Android client app for ntfy notifications, maintained in parallel repo
  • binwiederhier/ntfy-ios — Official iOS client app for ntfy notifications, companion repo for Apple platforms
  • gotify/server — Alternative self-hosted push notification service with similar HTTP API, direct competitor in same ecosystem
  • prometheus/alertmanager — Complementary alerting system often used alongside ntfy for ops workflows to route Prometheus alerts to mobile
  • apprise/apprise — Multi-backend notification library supporting 100+ services (Slack, Discord, email, etc.), overlaps use cases

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive integration tests for attachment backend implementations

The attachment package has two backend implementations (file and S3) with corresponding test files, but test coverage appears incomplete. There are test files for both backends (attachment/store_file_test.go, attachment/store_s3_test.go) but no visible tests for attachment/backend.go interface itself. Adding integration tests that verify both backends correctly implement the Backend interface and handle edge cases (corrupted files, S3 connectivity issues, permissions) would improve reliability of a critical feature.

  • [ ] Review existing tests in attachment/store_file_test.go and attachment/store_s3_test.go to identify coverage gaps
  • [ ] Create attachment/backend_test.go with interface compliance tests for both FileBackend and S3Backend
  • [ ] Add tests for error handling scenarios: invalid paths, S3 bucket not found, permission denied, storage quota exceeded
  • [ ] Add concurrent access tests to ensure thread-safety of backend implementations
  • [ ] Verify test coverage meets >80% for attachment/ package

Add missing unit tests for cmd/config_loader.go and platform-specific config files

Config management is critical for ntfy's multi-platform support (Darwin, Unix, Windows). There's a cmd/config_loader_test.go but the three platform-specific files (cmd/config_darwin.go, cmd/config_unix.go, cmd/config_windows.go) likely lack platform-specific test coverage. These files handle OS-specific configuration paths and behaviors that need dedicated tests to prevent regressions across platforms.

  • [ ] Examine cmd/config_loader_test.go to understand existing test patterns
  • [ ] Create cmd/config_darwin_test.go with tests for macOS-specific config paths and behaviors
  • [ ] Create cmd/config_unix_test.go with tests for Linux-specific config paths and XDG Base Directory compliance
  • [ ] Create cmd/config_windows_test.go with tests for Windows-specific registry/AppData paths
  • [ ] Add platform-specific integration tests that verify config loading works correctly on each OS (or use build tags)

Add CLI integration tests for cmd/publish_* and cmd/subscribe_* platform-specific implementations

The publish and subscribe commands have platform-specific variants (cmd/publish_unix.go, cmd/publish_windows.go, cmd/subscribe_darwin.go, cmd/subscribe_unix.go, cmd/subscribe_windows.go) that handle OS-specific notification delivery and terminal behavior. While cmd/publish_test.go and cmd/subscribe_test.go exist, they likely don't cover platform-specific code paths. Adding platform-specific integration tests would ensure notification delivery works correctly across all supported platforms.

  • [ ] Review cmd/publish_test.go and cmd/subscribe_test.go to identify platform-specific gaps
  • [ ] Create cmd/publish_unix_test.go with tests for Unix-specific notification mechanisms (likely uses system notifications)
  • [ ] Create cmd/publish_windows_test.go with tests for Windows-specific notification APIs
  • [ ] Create cmd/subscribe_unix_test.go and cmd/subscribe_darwin_test.go for platform-specific subscription behaviors
  • [ ] Add end-to-end tests that verify notifications actually reach the system notification daemon on each platform (or mock appropriately)

🌿Good first issues

  • Add missing test coverage for attachment/backend_s3.go—currently only attachment/store_s3_test.go exists but backend interface has untested error paths (credentials failure, bucket not found, quota exceeded). Write integration tests or mock tests covering S3Backend.Write() and Delete() failure modes.
  • Document env var configuration: create docs/config.md listing all supported env vars (FIRESTORE_PROJECT, POSTGRES_, SMTP_, S3_*) with examples and which are required vs optional. README only shows curl examples, ops need a reference.
  • Add client-side retry logic to client/client.go—currently client.go appears to make single POST/WebSocket attempts; add exponential backoff with jitter using golang.org/x/time (already imported) for network resilience, particularly for mobile clients with spotty connectivity.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 802c0a4 — Bump (binwiederhier)
  • 9fc96bf — Merge pull request #1712 from binwiederhier/dependabot/npm_and_yarn/web/i18next-http-backend-3.0.5 (binwiederhier)
  • fe8c9b8 — Bump i18next-http-backend from 1.4.5 to 3.0.5 in /web (dependabot[bot])
  • 5ad2431 — Bump (binwiederhier)
  • 2401e18 — Bump (binwiederhier)
  • fa83d68 — Tighten web push endpoint allow list (binwiederhier)
  • 1956c88 — Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web (binwiederhier)
  • eefd3d1 — Translated using Weblate (English (United Kingdom)) (usefulish)
  • e10ece9 — Added translation using Weblate (English (United Kingdom)) (usefulish)
  • ac09f98 — Translated using Weblate (Latvian) (Coool)

🔒Security observations

Failed to generate security analysis.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · binwiederhier/ntfy — RepoPilot