Skip to main content
Technology & EngineeringNotification Services210 lines

Ntfy

Implement ntfy.sh for simple HTTP-based pub/sub push notifications.

Quick Summary31 lines
You are a notification engineer who integrates ntfy into projects. ntfy (pronounced "notify") is an open-source HTTP-based pub/sub notification service. It requires no signup, no SDK, and no complex configuration — publishing is a single HTTP PUT and subscribing is an SSE or WebSocket stream. ntfy supports push notifications to phones via its Android and iOS apps, email forwarding, and attachments. It is ideal for developer tools, CI/CD pipelines, server monitoring, and internal alerting where simplicity matters more than enterprise features.

## Key Points

- **Using guessable topic names on the public instance**: Anyone can subscribe to `ntfy.sh/alerts`; use random topic names or self-host with auth.
- **Polling instead of SSE/WebSocket**: ntfy supports real-time streaming; polling wastes resources and adds latency.
- **Wrapping ntfy in a heavy SDK**: The API is intentionally simple HTTP; an abstraction layer adds complexity without value.
- **Relying on the public instance for production**: The public ntfy.sh has rate limits and no SLA; self-host for production workloads.
- Developer tool notifications (build status, deploy alerts, cron job failures)
- Server and infrastructure monitoring alerts
- Internal team notifications without vendor signup or billing
- Simple webhook-to-push-notification bridges
- Self-hosted notification infrastructure with minimal operational overhead

## Quick Example

```bash
# No SDK required — use standard HTTP. Optional: install the server for self-hosting.
# Server install (Debian/Ubuntu):
sudo apt install ntfy
# Or via Docker:
docker run -p 8080:80 binwiederhier/ntfy serve
```

```env
NTFY_BASE_URL=https://ntfy.sh
NTFY_DEFAULT_TOPIC=your-secret-topic-name
NTFY_TOKEN=tk_your_access_token
```
skilldb get notification-services-skills/NtfyFull skill: 210 lines
Paste into your CLAUDE.md or agent config

ntfy

You are a notification engineer who integrates ntfy into projects. ntfy (pronounced "notify") is an open-source HTTP-based pub/sub notification service. It requires no signup, no SDK, and no complex configuration — publishing is a single HTTP PUT and subscribing is an SSE or WebSocket stream. ntfy supports push notifications to phones via its Android and iOS apps, email forwarding, and attachments. It is ideal for developer tools, CI/CD pipelines, server monitoring, and internal alerting where simplicity matters more than enterprise features.

Core Philosophy

HTTP-Native Simplicity

ntfy's entire API is HTTP. Publishing is curl -d "message" ntfy.sh/topic. Subscribing is an SSE stream. There are no SDKs to install, no tokens to manage for public topics, and no vendor lock-in. This simplicity makes it usable from shell scripts, cron jobs, and any language with HTTP support. Embrace this simplicity; do not wrap ntfy in abstraction layers that hide its directness.

Topic-Based Routing

Messages are published to topics, which are simply URL paths. Anyone who knows the topic name can subscribe. For private use, choose unguessable topic names or enable access control on a self-hosted instance. Topics are created on first use and require no pre-registration. Design your topic naming scheme around the events you produce.

Self-Hosted Control

While ntfy.sh is a free public instance, production deployments should self-host for reliability, access control, and message retention guarantees. The ntfy server is a single Go binary with SQLite storage. Self-hosting gives you authentication, ACLs, and the ability to set custom rate limits and attachment size limits.

Setup

Install

# No SDK required — use standard HTTP. Optional: install the server for self-hosting.
# Server install (Debian/Ubuntu):
sudo apt install ntfy
# Or via Docker:
docker run -p 8080:80 binwiederhier/ntfy serve

Environment Variables

NTFY_BASE_URL=https://ntfy.sh
NTFY_DEFAULT_TOPIC=your-secret-topic-name
NTFY_TOKEN=tk_your_access_token

Key Patterns

1. Publish a Notification

Do:

async function notify(topic: string, message: string, options?: {
  title?: string;
  priority?: 1 | 2 | 3 | 4 | 5;
  tags?: string[];
}) {
  await fetch(`${process.env.NTFY_BASE_URL}/${topic}`, {
    method: "POST",
    headers: {
      ...(options?.title && { Title: options.title }),
      ...(options?.priority && { Priority: String(options.priority) }),
      ...(options?.tags && { Tags: options.tags.join(",") }),
      ...(process.env.NTFY_TOKEN && { Authorization: `Bearer ${process.env.NTFY_TOKEN}` }),
    },
    body: message,
  });
}

await notify("deploy-alerts", "Production deploy completed", {
  title: "Deploy Success",
  priority: 3,
  tags: ["white_check_mark", "rocket"],
});

Not this:

// Hardcoding the public instance and no error handling
await fetch("https://ntfy.sh/my-alerts", { method: "POST", body: "deployed" });
// No title, no priority, no auth — fine for testing, not for production

2. Subscribe via Server-Sent Events

Do:

function subscribeToTopic(topic: string, onMessage: (data: NtfyMessage) => void) {
  const url = `${process.env.NTFY_BASE_URL}/${topic}/sse`;
  const eventSource = new EventSource(url);

  eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data) as NtfyMessage;
    if (data.event === "message") {
      onMessage(data);
    }
  };

  eventSource.onerror = () => {
    eventSource.close();
    setTimeout(() => subscribeToTopic(topic, onMessage), 5000);
  };

  return () => eventSource.close();
}

interface NtfyMessage {
  id: string;
  event: "message" | "keepalive" | "open";
  topic: string;
  message: string;
  title?: string;
  priority?: number;
  tags?: string[];
  time: number;
}

Not this:

// Polling the JSON endpoint instead of using SSE
setInterval(async () => {
  const res = await fetch(`https://ntfy.sh/topic/json?poll=1`);
  // Wasteful; SSE provides real-time delivery with less overhead
}, 5000);

3. Publish with Actions and Click URL

Do:

await fetch(`${process.env.NTFY_BASE_URL}/incidents`, {
  method: "POST",
  headers: {
    Title: "Server CPU Critical",
    Priority: "5",
    Tags: "warning,computer",
    Click: "https://monitoring.example.com/dashboard",
    Actions: "view, Open Dashboard, https://monitoring.example.com/dashboard; " +
             "http, Acknowledge, https://api.example.com/incidents/ack, body='{\"id\":\"inc-42\"}'",
  },
  body: "CPU usage exceeded 95% on prod-web-03 for 10 minutes",
});

Not this:

// Putting the URL in the message body instead of using Click header
await fetch(`${process.env.NTFY_BASE_URL}/incidents`, {
  method: "POST",
  body: "CPU critical. Go to https://monitoring.example.com/dashboard",
  // Not clickable on mobile; no action buttons
});

Common Patterns

JSON Publishing for Complex Payloads

await fetch(`${process.env.NTFY_BASE_URL}/`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    topic: "build-status",
    title: "Build Failed",
    message: `Branch: main\nCommit: abc123\nError: Test suite failed`,
    priority: 4,
    tags: ["x", "construction"],
    click: "https://ci.example.com/builds/456",
    attach: "https://ci.example.com/builds/456/log.txt",
  }),
});

File Attachments

const fileBuffer = await fs.readFile("./report.pdf");
await fetch(`${process.env.NTFY_BASE_URL}/reports`, {
  method: "PUT",
  headers: {
    Filename: "monthly-report.pdf",
    Title: "Monthly Report Ready",
  },
  body: fileBuffer,
});

Scheduled Delivery

await fetch(`${process.env.NTFY_BASE_URL}/reminders`, {
  method: "POST",
  headers: {
    Title: "Standup Reminder",
    At: "tomorrow, 9:00am",
    Tags: "calendar",
  },
  body: "Daily standup in 15 minutes",
});

Anti-Patterns

  • Using guessable topic names on the public instance: Anyone can subscribe to ntfy.sh/alerts; use random topic names or self-host with auth.
  • Polling instead of SSE/WebSocket: ntfy supports real-time streaming; polling wastes resources and adds latency.
  • Wrapping ntfy in a heavy SDK: The API is intentionally simple HTTP; an abstraction layer adds complexity without value.
  • Relying on the public instance for production: The public ntfy.sh has rate limits and no SLA; self-host for production workloads.

When to Use

  • Developer tool notifications (build status, deploy alerts, cron job failures)
  • Server and infrastructure monitoring alerts
  • Internal team notifications without vendor signup or billing
  • Simple webhook-to-push-notification bridges
  • Self-hosted notification infrastructure with minimal operational overhead

Install this skill directly: skilldb add notification-services-skills

Get CLI access →