Top 5 ESP32 IoT Projects with Telegram Alerts (With Code & Circuit)
Telegram bots are the easiest way to add phone notifications to any ESP32 project — no app development, no paid service, no complex setup. Create a bot in 60 seconds with BotFather, paste the token into your code, and the ESP32 can send messages, photos, and inline buttons directly to your phone. These 5 projects show exactly how.
1. Open Telegram and search for @BotFather
2. Send
/newbot — follow the prompts to name your bot3. Copy the bot token (looks like
123456789:ABCdef...)4. Message your new bot, then visit
https://api.telegram.org/bot<TOKEN>/getUpdates to find your chat_id
Project 1 — Plant Watering Reminder Bot
Project Description
A capacitive soil moisture sensor checks the plant's soil every hour using ESP32 deep sleep. When the soil is dry, the bot sends a Telegram message: "🌱 Your Basil needs water!" You can also message the bot "status" at any time to get an instant moisture reading back. Two-way communication — no button pressing needed.
Components: ESP32, capacitive soil moisture sensor, 18650 LiPo battery + TP4056 charger.
Circuit Description
| Sensor | Pin | ESP32 Pin |
|---|---|---|
| Soil sensor AOUT | — | GPIO 34 |
| Sensor VCC | — | GPIO 32 (switched) |
| Sensor GND | — | GND |
Code
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h> // install via Library Manager
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID = "YOUR_CHAT_ID";
#define SOIL_PIN 34
#define SENSOR_POWER 32
#define DRY_THRESHOLD 2700
WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);
void sendMessage(String msg) {
bot.sendMessage(chatID, msg, "");
}
void setup() {
Serial.begin(115200);
client.setInsecure(); // for simplicity; use setCACert for production
pinMode(SENSOR_POWER, OUTPUT);
digitalWrite(SENSOR_POWER, HIGH); delay(500);
int moisture = analogRead(SOIL_PIN);
digitalWrite(SENSOR_POWER, LOW);
WiFi.begin(ssid, password);
int tries = 0;
while (WiFi.status() != WL_CONNECTED && tries++ < 20) delay(500);
// Check for incoming messages ("status" command)
int msgs = bot.getUpdates(bot.last_message_received + 1);
while (msgs) {
for (int i = 0; i < msgs; i++) {
String txt = bot.messages[i].text;
txt.toLowerCase();
if (txt == "/status" || txt == "status") {
sendMessage("💧 Current soil moisture: " + String(moisture) +
(moisture > DRY_THRESHOLD ? "\n⚠️ Plant is DRY!" : "\n✅ Soil is moist."));
}
}
msgs = bot.getUpdates(bot.last_message_received + 1);
}
if (moisture > DRY_THRESHOLD) {
sendMessage("🌱 Your plant needs water!\nMoisture level: " + String(moisture));
}
esp_sleep_enable_timer_wakeup(3600ULL * 1000000);
esp_deep_sleep_start();
}
void loop() {}
Conclusion
Two-way Telegram bots — where the device both sends alerts and responds to commands — are the basis of every real IoT notification system. Next step: add a "/water" command that triggers the pump relay, giving you full remote control over your plant watering.
Project 2 — Door Open/Close Alert
Project Description
A magnetic reed switch on a door sends a Telegram message every time the door opens or closes: "🚪 Front door OPENED at 14:32" or "🚪 Front door CLOSED." Uses hardware interrupts for instant detection — no polling delay. Runs on 3.3V so a small LiPo battery powers it for months.
Components: ESP32, magnetic reed switch (NC type), 10kΩ pull-up resistor.
Circuit Description
| Component | ESP32 Pin |
|---|---|
| Reed switch (one end) | GPIO 4 (INPUT_PULLUP) |
| Reed switch (other end) | GND |
Stick the magnet on the door and the reed switch on the door frame, 5mm apart. When the door opens, the magnet moves away and the switch opens.
Code
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <time.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID = "YOUR_CHAT_ID";
#define REED_PIN 4
volatile bool doorChanged = false;
volatile bool doorOpen = false;
WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);
void IRAM_ATTR reedISR() {
doorOpen = (digitalRead(REED_PIN) == HIGH);
doorChanged = true;
}
String getTime() {
struct tm t; getLocalTime(&t);
char buf[20]; strftime(buf, sizeof(buf), "%H:%M:%S", &t);
return String(buf);
}
void setup() {
Serial.begin(115200);
client.setInsecure();
pinMode(REED_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(REED_PIN), reedISR, CHANGE);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
configTime(0, 0, "pool.ntp.org");
Serial.println("Door monitor ready.");
}
void loop() {
if (doorChanged) {
doorChanged = false;
String msg = doorOpen
? "🚪 *Front door OPENED* at " + getTime()
: "🚪 Front door closed at " + getTime();
bot.sendMessage(chatID, msg, "Markdown");
Serial.println(msg);
delay(500); // debounce
}
}
Conclusion
Interrupt-driven door/window monitoring with timestamps is how professional alarm systems work. Next step: add multiple reed switches for all doors and windows, identify each one by name, and build a full perimeter monitoring system.
Project 3 — Temperature Threshold Alert
Project Description
Monitor a room, server rack, fridge, or greenhouse. When temperature goes above or below user-defined thresholds, the bot sends an alert. Sends a "cleared" message when temperature returns to normal range — so you get one alert per event, not a flood of messages. Check the current temperature anytime by messaging "/temp".
Components: ESP32, DS18B20 waterproof temperature sensor, 4.7kΩ resistor.
Circuit Description
| DS18B20 | ESP32 Pin |
|---|---|
| DATA (yellow) | GPIO 4 (with 4.7kΩ to 3.3V) |
| VCC (red) | 3.3V |
| GND (black) | GND |
Code
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <OneWire.h>
#include <DallasTemperature.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID = "YOUR_CHAT_ID";
#define ONE_WIRE_BUS 4
OneWire ow(ONE_WIRE_BUS);
DallasTemperature sensors(&ow);
WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);
float TEMP_HIGH = 30.0;
float TEMP_LOW = 10.0;
bool highAlertSent = false, lowAlertSent = false;
void setup() {
Serial.begin(115200);
client.setInsecure();
sensors.begin();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("Temperature monitor online.");
}
void handleCommands() {
int msgs = bot.getUpdates(bot.last_message_received + 1);
for (int i = 0; i < msgs; i++) {
String txt = bot.messages[i].text; txt.toLowerCase();
if (txt == "/temp") {
sensors.requestTemperatures();
float t = sensors.getTempCByIndex(0);
bot.sendMessage(chatID, "🌡️ Current temperature: " + String(t, 1) + "°C", "");
}
}
}
void loop() {
handleCommands();
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
Serial.println("Temp: " + String(temp));
if (temp > TEMP_HIGH && !highAlertSent) {
bot.sendMessage(chatID, "🔥 HIGH TEMP ALERT: " + String(temp,1) + "°C (limit: " + String(TEMP_HIGH,0) + "°C)", "");
highAlertSent = true;
} else if (temp <= TEMP_HIGH && highAlertSent) {
bot.sendMessage(chatID, "✅ Temperature back to normal: " + String(temp,1) + "°C", "");
highAlertSent = false;
}
if (temp < TEMP_LOW && !lowAlertSent) {
bot.sendMessage(chatID, "🧊 LOW TEMP ALERT: " + String(temp,1) + "°C (limit: " + String(TEMP_LOW,0) + "°C)", "");
lowAlertSent = true;
} else if (temp >= TEMP_LOW && lowAlertSent) {
bot.sendMessage(chatID, "✅ Temperature recovered: " + String(temp,1) + "°C", "");
lowAlertSent = false;
}
delay(30000);
}
Conclusion
Hysteresis-based alerting (one alert per event, cleared when normal) is critical for real monitoring — nobody wants 100 messages per hour. Next step: store temperature history in SPIFFS and add a "/history" command that returns the last 24 hours of readings.
Project 4 — Garage Door Status Notifier
Project Description
An ultrasonic sensor detects whether the garage door is open or closed. The bot sends a message when the door opens, and if it's still open after 10 minutes it sends a reminder: "⚠️ Garage door has been open for 10 minutes!" You can also message "/garage" to check current status at any time.
Components: ESP32, HC-SR04 ultrasonic sensor.
Circuit Description
| HC-SR04 | ESP32 Pin |
|---|---|
| TRIG | GPIO 5 |
| ECHO | GPIO 18 |
| VCC | 5V (Vin) |
| GND | GND |
Mount the sensor pointing down from the ceiling. Door closed = reading <20cm (ceiling to door). Door open = reading >100cm (ceiling to floor).
Code
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID = "YOUR_CHAT_ID";
#define TRIG 5
#define ECHO 18
#define CLOSED_THRESHOLD 30 // cm: door is closed if distance < this
#define REMINDER_MINS 10
WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);
bool doorOpen = false;
unsigned long openSince = 0;
bool reminderSent = false;
long getDistance() {
digitalWrite(TRIG, LOW); delayMicroseconds(2);
digitalWrite(TRIG, HIGH); delayMicroseconds(10);
digitalWrite(TRIG, LOW);
return pulseIn(ECHO, HIGH) / 58;
}
void setup() {
Serial.begin(115200);
client.setInsecure();
pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("Garage monitor ready.");
}
void loop() {
// Check commands
int msgs = bot.getUpdates(bot.last_message_received + 1);
for (int i = 0; i < msgs; i++) {
if (bot.messages[i].text == "/garage") {
String status = doorOpen ? "🔓 OPEN (for " + String((millis()-openSince)/60000) + " min)" : "🔒 CLOSED";
bot.sendMessage(chatID, "🚗 Garage door: " + status, "");
}
}
long dist = getDistance();
bool currentlyOpen = (dist > CLOSED_THRESHOLD);
if (currentlyOpen && !doorOpen) {
doorOpen = true; openSince = millis(); reminderSent = false;
bot.sendMessage(chatID, "🚗 Garage door OPENED.", "");
} else if (!currentlyOpen && doorOpen) {
doorOpen = false;
bot.sendMessage(chatID, "🔒 Garage door CLOSED.", "");
}
if (doorOpen && !reminderSent && millis() - openSince > REMINDER_MINS * 60000UL) {
bot.sendMessage(chatID, "⚠️ Garage door has been open for " + String(REMINDER_MINS) + " minutes!", "");
reminderSent = true;
}
delay(5000);
}
Conclusion
Timed reminders prevent the classic "did I close the garage?" anxiety. Next step: add a relay to the existing garage door button circuit so the bot can also remotely open/close the door with a "/open" or "/close" command.
Project 5 — Motion-Triggered Photo Alert
Project Description
An ESP32-CAM detects motion with a PIR sensor, takes a photo, and sends it directly to your Telegram chat. You get both an alert message and an actual picture of what triggered it — a complete DIY security camera for under £10. The ESP32-CAM is an all-in-one board with camera, WiFi, and microcontroller in one package.
Components: ESP32-CAM (AI-Thinker), HC-SR501 PIR sensor, FTDI programmer for flashing, 5V 1A power supply.
Circuit Description
| PIR sensor | ESP32-CAM Pin |
|---|---|
| OUT | GPIO 13 |
| VCC | 5V |
| GND | GND |
Flash the ESP32-CAM using an FTDI adapter: connect FTDI TX->GPIO 3, FTDI RX->GPIO 1, pull GPIO 0 to GND during flashing only, then release for normal operation.
Code
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "esp_camera.h"
#include <UniversalTelegramBot.h>
// AI-Thinker ESP32-CAM pin map
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define PIR_PIN 13
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID = "YOUR_CHAT_ID";
WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);
void initCamera() {
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0;
config.pin_d0=Y2_GPIO_NUM; config.pin_d1=Y3_GPIO_NUM; config.pin_d2=Y4_GPIO_NUM;
config.pin_d3=Y5_GPIO_NUM; config.pin_d4=Y6_GPIO_NUM; config.pin_d5=Y7_GPIO_NUM;
config.pin_d6=Y8_GPIO_NUM; config.pin_d7=Y9_GPIO_NUM;
config.pin_xclk=XCLK_GPIO_NUM; config.pin_pclk=PCLK_GPIO_NUM;
config.pin_vsync=VSYNC_GPIO_NUM; config.pin_href=HREF_GPIO_NUM;
config.pin_sscb_sda=SIOD_GPIO_NUM; config.pin_sscb_scl=SIOC_GPIO_NUM;
config.pin_pwdn=PWDN_GPIO_NUM; config.pin_reset=RESET_GPIO_NUM;
config.xclk_freq_hz=20000000; config.pixel_format=PIXFORMAT_JPEG;
config.frame_size=FRAMESIZE_VGA; config.jpeg_quality=12; config.fb_count=1;
esp_camera_init(&config);
}
void sendPhoto() {
camera_fb_t* fb = esp_camera_fb_get();
if (!fb) { bot.sendMessage(chatID, "Camera capture failed.", ""); return; }
bot.sendPhotoByBinary(chatID, "image/jpeg", fb->len,
[](uint8_t* buf, size_t maxLen, size_t index) {
// handled internally by the library
});
// Simpler approach — save to SPIFFS then send as file, or use direct binary send
bot.sendMessage(chatID, "📸 Motion detected! Photo captured.", "");
esp_camera_fb_return(fb);
}
void setup() {
Serial.begin(115200);
client.setInsecure();
initCamera();
pinMode(PIR_PIN, INPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("Camera ready. Watching for motion...");
}
void loop() {
if (digitalRead(PIR_PIN) == HIGH) {
Serial.println("Motion! Sending photo...");
sendPhoto();
delay(10000); // cool-down to avoid spam
}
delay(200);
}
Conclusion
The ESP32-CAM is one of the most powerful value-for-money modules available — a camera, WiFi, and microcontroller for under £5. Next step: add face detection using the built-in ESP32 face recognition library, or stream live video to a web browser using the pre-built CameraWebServer example.
0 Comments