A doorbell is easy to miss when you are listening to music upstairs. This one sends your phone a notification as well as ringing. It uses the button module from the sensor kit, the passive buzzer for a two-tone "ding-dong", and ntfy for the notification, just as in the motion alert lesson. An LED blinks to show the visitor that the message went through.
What you'll learn
- How to react to a button press only once, even if it is held
- How to play a two-tone chime
- How to send a notification with a priority and an emoji
- How to stop pranksters from flooding your phone
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- Button module (45-in-1 Sensor Kit)
- Passive buzzer (ESP32 IoT Starter Kit)
- 1 LED and 1 resistor, 220 Ω (ESP32 IoT Starter Kit)
- A phone with the free ntfy app
- A 2.4 GHz Wi-Fi network
- The printed parts from the 3D printing box below (doorbell case), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| Button module S | ESP32 GPIO 27 |
| Button module + and − | 3V3 and GND |
| Passive buzzer + | ESP32 GPIO 23 |
| Passive buzzer − | GND |
| ESP32 GPIO 25 | 220 Ω resistor, then the LED's long leg |
| LED short leg | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
Set up ntfy
Install the ntfy app and subscribe to a long, random topic name such as eementor-door-q7x2m4. Topics on the free server are public, so never use something easy to guess.
The sketch
Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only).
Put your topic into TOPIC.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* TOPIC = "YOUR_SECRET_TOPIC";
const int BUTTON_PIN = 27;
const int BUZZER_PIN = 23;
const int LED_PIN = 25;
const unsigned long QUIET_TIME = 15000; // at most one notification every 15 s
int restReading;
unsigned long lastRing = 0;
bool rungOnce = false;
void dingDong() {
tone(BUZZER_PIN, 784, 350); // G
delay(400);
tone(BUZZER_PIN, 622, 600); // E flat
delay(650);
}
bool notify() {
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
http.begin(client, String("https://ntfy.sh/") + TOPIC);
http.addHeader("Title", "Someone is at the door");
http.addHeader("Tags", "bell");
http.addHeader("Priority", "urgent");
int code = http.POST("The doorbell was pressed.");
http.end();
Serial.printf("ntfy answered %d\n", code);
return code == 200;
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
restReading = digitalRead(BUTTON_PIN); // works with modules that idle HIGH or LOW
WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" connected. Ready!");
}
void loop() {
if (digitalRead(BUTTON_PIN) != restReading) {
dingDong();
if (!rungOnce || millis() - lastRing > QUIET_TIME) {
rungOnce = true;
lastRing = millis();
bool sent = notify();
for (int i = 0; i < (sent ? 3 : 10); i++) { // 3 slow blinks: sent, fast blinks: failed
digitalWrite(LED_PIN, HIGH); delay(sent ? 200 : 60);
digitalWrite(LED_PIN, LOW); delay(sent ? 200 : 60);
}
}
while (digitalRead(BUTTON_PIN) != restReading) delay(10); // wait for the release
}
}
How it works
The sketch reads the button once at the start to learn its "resting" value, so it works whether your module gives HIGH or LOW when pressed. After a ring it waits until the button is released, so holding the button rings only once.
Every press plays the chime, but the notification is limited to one every 15 seconds: someone pressing the button again and again can't flood your phone. The urgent priority makes ntfy show the message even when your phone is set to "do not disturb" for normal notifications (you can change that in the app).
client.setInsecure() keeps the connection encrypted but skips checking the server's certificate. That keeps the sketch short and is fine for a learning project. For a device you leave running for years, load the server's root certificate with client.setCACert() instead.
Try this next
- Send a different message at night ("Someone rang at 23:14").
- Add a second button for a "parcel" message.
- Count rings per day in Google Sheets, like the noise logger.
Troubleshooting
- It rings by itself at startup: don't press the button while the ESP32 starts; it learns the resting value then.
- The chime plays but no notification arrives: check the topic in the sketch and in the app letter by letter.
- The LED blinks fast: sending failed. Check the internet connection.
- It never connects to Wi-Fi: check the network name and password (both are case-sensitive) and make sure the network is 2.4 GHz.
- A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.