ntfy.sh — Push Notifications from ESP32

Send instant push notifications to your phone from ESP32 using ntfy.sh — no account, no API key, no setup

Sometimes you do not need a dashboard or a database — you just need your ESP32 to tap you on the shoulder and say "the temperature is too high" or "someone opened the door." That is exactly what ntfy does. One HTTP POST from your ESP32, and a push notification appears on your phone. No account, no API key, no setup.

🔗What is ntfy?

ntfy (pronounced "notify") is a free, open-source push notification service. It works like this:

  1. You pick a topic name — any string you want, like my-esp32-alerts.
  2. Your ESP32 sends an HTTP POST to https://ntfy.sh/your-topic-name.
  3. Anyone subscribed to that topic gets a push notification instantly.

That is the entire concept. There are no accounts, no API keys, no OAuth flows, no tokens to refresh.

ntfy has apps for Android (Google Play and F-Droid) and iOS (App Store), and you can also receive notifications in any web browser at ntfy.sh. The server is open-source, so you can self-host it if you prefer.

🔗What You'll Need

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
Push button (tactile)1For testing (or use any sensor)AliExpress | Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires~3AliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

You will also need:

  • The ntfy app installed on your phone (Android / iOS)
  • No account registration needed

🔗How It Works

graph LR
    A[ESP32] -->|HTTP POST| B[ntfy.sh server]
    B -->|Push notification| C[Your phone]
    B -->|Web notification| D[Browser]

The flow is simple: your ESP32 sends an HTTP POST request to the ntfy server with the notification text as the body. The server forwards it as a push notification to every device subscribed to that topic.

Warning: Topics are public by default. Anyone who knows your topic name can subscribe to it and see your messages. Do not use common names like test, temperature, or esp32 — other people are probably already using them. Pick something unique, like jims-garage-sensor-2026 or a random string. For private topics, you can set up authentication on a self-hosted ntfy server.

🔗Setting Up Your Phone

  1. Install the ntfy app on your phone.
  2. Open the app and tap Subscribe to topic.
  3. Enter a unique topic name — for example, my-esp32-alerts-abc123. Use the same name in your ESP32 code.
  4. That is it. Any message posted to that topic will now appear as a push notification.

🔗Code Example: Send a Notification

This minimal sketch connects to WiFi and sends a push notification when it boots up. It is the simplest possible starting point.

View complete sketch
#include <WiFi.h>
#include <HTTPClient.h>

// ----- WiFi credentials -----
const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// ----- ntfy settings -----
// Pick a unique topic name and subscribe to it in the ntfy app
const char* ntfyTopic = "my-esp32-alerts-abc123";

void connectToWiFi() {
  Serial.printf("Connecting to %s", ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\nConnected! IP: %s\n",
                  WiFi.localIP().toString().c_str());
  } else {
    Serial.println("\nFailed to connect to WiFi.");
  }
}

void sendNotification(const char* message) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi not connected. Skipping notification.");
    return;
  }

  HTTPClient http;

  String url = "https://ntfy.sh/";
  url += ntfyTopic;

  http.begin(url);
  http.addHeader("Title", "ESP32 Alert");
  http.addHeader("Priority", "default");
  http.addHeader("Tags", "zap");

  int httpCode = http.POST(message);

  if (httpCode == 200) {
    Serial.printf("Notification sent: %s\n", message);
  } else {
    Serial.printf("Failed to send notification: HTTP %d\n", httpCode);
  }

  http.end();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  connectToWiFi();

  // Send a test notification on boot
  sendNotification("ESP32 is online and running!");
}

void loop() {
  // Your sensor reading and alert logic goes here
}

🔗How the Code Works

The key part is the sendNotification() function. It does three things:

  1. Opens an HTTP connection to https://ntfy.sh/your-topic-name.
  2. Adds optional headers for the notification title, priority level, and emoji tags.
  3. POSTs the message as the request body — this becomes the notification text on your phone.

That is it. The ntfy server receives the POST and pushes it to all subscribed devices. The response is HTTP 200 on success.

🔗Sensor Alerts with Cooldown

In a real project, you want to send a notification when a sensor value crosses a threshold — but you do not want to get spammed with alerts every few seconds while the condition persists. The solution is a cooldown timer: after sending an alert, wait a minimum amount of time before sending another one.

View complete sketch — sensor alerts with cooldown
#include <WiFi.h>
#include <HTTPClient.h>

// ----- WiFi credentials -----
const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// ----- ntfy settings -----
const char* ntfyTopic = "my-esp32-alerts-abc123";

// ----- Alert settings -----
const float tempThreshold    = 30.0;  // Alert when above 30 °C
const unsigned long cooldown = 300000; // 5 minutes between alerts

unsigned long lastAlertTime = 0;
bool alertSent = false;

void connectToWiFi() {
  Serial.printf("Connecting to %s", ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\nConnected! IP: %s\n",
                  WiFi.localIP().toString().c_str());
  } else {
    Serial.println("\nFailed to connect to WiFi.");
  }
}

void sendNotification(const char* title, const char* message,
                      const char* priority, const char* tags) {
  if (WiFi.status() != WL_CONNECTED) return;

  HTTPClient http;

  String url = "https://ntfy.sh/";
  url += ntfyTopic;

  http.begin(url);
  http.addHeader("Title", title);
  http.addHeader("Priority", priority);
  http.addHeader("Tags", tags);

  int httpCode = http.POST(message);

  if (httpCode == 200) {
    Serial.printf("Notification sent: %s\n", message);
  } else {
    Serial.printf("Failed: HTTP %d\n", httpCode);
  }

  http.end();
}

float readTemperature() {
  // Replace this with your actual sensor reading.
  // For testing, we simulate a temperature that slowly rises.
  static float simTemp = 25.0;
  simTemp += 0.5;
  if (simTemp > 40.0) simTemp = 25.0;
  return simTemp;
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  connectToWiFi();
  sendNotification("ESP32 Sensor", "Sensor monitor started.",
                   "default", "white_check_mark");
}

void loop() {
  float temperature = readTemperature();
  Serial.printf("Temperature: %.1f °C\n", temperature);

  unsigned long now = millis();

  if (temperature > tempThreshold) {
    // Only send if cooldown has elapsed
    if (!alertSent || (now - lastAlertTime >= cooldown)) {
      char message[64];
      snprintf(message, sizeof(message),
               "Temperature is %.1f °C (threshold: %.1f °C)",
               temperature, tempThreshold);

      sendNotification("High Temperature!", message, "high", "fire");

      lastAlertTime = now;
      alertSent = true;
    }
  } else {
    // Temperature is back to normal — reset so we alert again if it rises
    if (alertSent) {
      sendNotification("Temperature Normal",
                       "Temperature has returned to a safe level.",
                       "default", "white_check_mark");
      alertSent = false;
    }
  }

  delay(10000);  // Check every 10 seconds
}

🔗Key Patterns in This Sketch

  • Threshold check — the alert only fires when the temperature exceeds the defined limit.
  • Cooldown timer — after sending an alert, the sketch waits 5 minutes before sending another, even if the temperature stays high. This prevents notification spam.
  • Recovery notification — when the temperature drops back below the threshold, a "back to normal" notification is sent and the cooldown resets.
  • Simulated sensor — the readTemperature() function simulates a rising temperature for testing. Replace it with an actual sensor reading (e.g., from a BME280 or DHT22).

Tip: Use priority "high" or "urgent" for important alerts. The ntfy app will override Do Not Disturb on Android for urgent-priority notifications, which is exactly what you want for a smoke alarm or flood sensor.

🔗Rich Notifications

ntfy supports several HTTP headers that customize how the notification looks and behaves on your phone.

HeaderPurposeExample Value
TitleBold title line above the message"Garage Door Open"
PriorityUrgency level (1-5 or named)"urgent", "high", "default", "low", "min"
TagsEmoji shortcodes shown as icons"fire", "warning", "white_check_mark"
ClickURL to open when notification is tapped"https://io.adafruit.com/dashboard"
AttachURL of an image or file to attach"https://example.com/photo.jpg"

🔗Priority Levels

PriorityNameBehavior
5urgentOverrides Do Not Disturb, persistent notification sound
4highProminent notification
3defaultStandard notification
2lowQuieter, lower in the list
1minSilent, no sound or vibration

Here is how you might use different priorities in practice:

// Fire/smoke alarm — needs immediate attention
http.addHeader("Title", "SMOKE DETECTED");
http.addHeader("Priority", "urgent");
http.addHeader("Tags", "rotating_light,fire");
http.POST("Smoke sensor triggered in the garage!");
// Daily summary — informational, no rush
http.addHeader("Title", "Daily Summary");
http.addHeader("Priority", "low");
http.addHeader("Tags", "bar_chart");
http.POST("Min: 18.2 °C | Max: 24.7 °C | Avg humidity: 52%");

The Tags header accepts any emoji shortcode — the same codes used on GitHub and Slack. Common ones for IoT: fire, warning, droplet, thermometer, door, rotating_light, white_check_mark.

🔗Troubleshooting

ProblemPossible CauseSolution
No notification on phoneNot subscribed to the right topicCheck the topic name in the ntfy app matches your code exactly (case-sensitive)
No notification on phoneApp not running or notifications disabledCheck Android/iOS notification settings for the ntfy app
Notification delayedNetwork latency or phone in Doze modeAndroid may delay notifications to save battery — try priority "high" or "urgent"
HTTP 429 (rate limited)Too many requests too fastntfy has a default rate limit of 250 messages/hour per topic on the public server; add a cooldown
HTTP 400 (bad request)Invalid header valueCheck that priority is a valid value (1-5 or a named level)
Messages visible to othersTopic names are publicUse a unique, hard-to-guess topic name, or self-host ntfy for private use
HTTPS connection failsMissing root certificatesMake sure you are using a recent ESP32 Arduino core (2.0+) which includes root CA certificates

🔗What's Next?

A powerful combination is to use Adafruit IO for logging and dashboards (so you can see trends over time) and ntfy for instant alerts (so you know immediately when something needs attention). They serve different purposes and work well together.