Build a Wi-Fi doorbell that rings your phone

Press the button and the doorbell plays a ding-dong and sends a notification to your phone with ntfy, so you hear the door even with headphones on.
  • Level:Intermediate
  • Duration: 50 min
  • Ages 11+
  • Platform: ESP32

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a Wi-Fi doorbell that rings your phone – EEMENTOR STEM™ lesson

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

Breadboard wiring diagram: Build a Wi-Fi doorbell that rings your phone
Breadboard view. Check each connection against the wiring table below.
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.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

ESP32 IoT Starter Kit | Arduino IDE Compatible | Free Tutorials $57.00

Shop the kit