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:
- You pick a topic name — any string you want, like
my-esp32-alerts. - Your ESP32 sends an HTTP POST to
https://ntfy.sh/your-topic-name. - 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
| Component | Qty | Notes | Buy |
|---|---|---|---|
| ESP32 dev board | 1 | AliExpress | Amazon.de .co.uk .com | |
| Push button (tactile) | 1 | For testing (or use any sensor) | AliExpress | Amazon.de .co.uk .com |
| Breadboard | 1 | AliExpress | Amazon.de .co.uk .com | |
| Jumper wires | ~3 | AliExpress | 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:
🔗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, oresp32— other people are probably already using them. Pick something unique, likejims-garage-sensor-2026or a random string. For private topics, you can set up authentication on a self-hosted ntfy server.
🔗Setting Up Your Phone
- Install the ntfy app on your phone.
- Open the app and tap Subscribe to topic.
- Enter a unique topic name — for example,
my-esp32-alerts-abc123. Use the same name in your ESP32 code. - 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:
- Opens an HTTP connection to
https://ntfy.sh/your-topic-name. - Adds optional headers for the notification title, priority level, and emoji tags.
- 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.
| Header | Purpose | Example Value |
|---|---|---|
Title | Bold title line above the message | "Garage Door Open" |
Priority | Urgency level (1-5 or named) | "urgent", "high", "default", "low", "min" |
Tags | Emoji shortcodes shown as icons | "fire", "warning", "white_check_mark" |
Click | URL to open when notification is tapped | "https://io.adafruit.com/dashboard" |
Attach | URL of an image or file to attach | "https://example.com/photo.jpg" |
🔗Priority Levels
| Priority | Name | Behavior |
|---|---|---|
| 5 | urgent | Overrides Do Not Disturb, persistent notification sound |
| 4 | high | Prominent notification |
| 3 | default | Standard notification |
| 2 | low | Quieter, lower in the list |
| 1 | min | Silent, 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
| Problem | Possible Cause | Solution |
|---|---|---|
| No notification on phone | Not subscribed to the right topic | Check the topic name in the ntfy app matches your code exactly (case-sensitive) |
| No notification on phone | App not running or notifications disabled | Check Android/iOS notification settings for the ntfy app |
| Notification delayed | Network latency or phone in Doze mode | Android may delay notifications to save battery — try priority "high" or "urgent" |
| HTTP 429 (rate limited) | Too many requests too fast | ntfy has a default rate limit of 250 messages/hour per topic on the public server; add a cooldown |
| HTTP 400 (bad request) | Invalid header value | Check that priority is a valid value (1-5 or a named level) |
| Messages visible to others | Topic names are public | Use a unique, hard-to-guess topic name, or self-host ntfy for private use |
| HTTPS connection fails | Missing root certificates | Make sure you are using a recent ESP32 Arduino core (2.0+) which includes root CA certificates |
🔗What's Next?
- Adafruit IO Tutorial — add cloud dashboards and data logging alongside your ntfy alerts
- MQTT Protocol Reference — learn the lightweight messaging protocol used by most IoT systems
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.