Beta — ESP32-based Telegram bot with a web dashboard for managing custom commands, API integration, media attachments, and inline keyboards.
Telesp turns an ESP32 into a full-featured Telegram bot. Manage everything through a clean web interface — no coding required after initial flash.
- Custom Commands — Create any
/commandwith a custom reply - API Integration — Call any REST API when a command is invoked, use response data in replies
- Placeholder System — Dynamic placeholders:
{first_name},{query},{time},{response:key.path},{eval:expression} - Media Attachments — Send photos, videos, documents, or audio from URLs
- Inline Buttons — URL buttons and callback buttons with placeholder support
- Web Dashboard — Full management UI served directly from the ESP32
- Wi-Fi Setup Wizard — First-boot AP mode with step-by-step browser setup
- NTP Sync — Real-time clock for date/time placeholders
- OTA Configurable — All settings changeable from the dashboard (timezone, Wi-Fi, bot token)
- Import/Export — Full config import via JSON file
| Component | Requirement |
|---|---|
| Board | ESP32-C3 (ESP32-C3-DevKitM-1) |
| Flash | 4MB+ |
| PSRAM | Not required |
| Power | USB-C (5V) |
ESP32 (Xtensa) variants should also work with minor platform changes.
# Clone or copy the project
cd telesp
# Build firmware
pio run
# Upload firmware
pio run --target upload
# Upload filesystem (web UI files)
pio run --target uploadfs- Power the ESP32 via USB
- On first boot, it creates a Wi-Fi AP named
ESP32_Bot_Setup - Connect your phone/laptop to that network
- Open http://192.168.4.1 in a browser
Step 1 — Wi-Fi: Select your network, enter password, click Connect. Wait for the ESP to join your network.
Step 2 — Telegram: Create a bot via @BotFather on Telegram, copy the token, paste it into the wizard. The ESP verifies the token automatically.
Step 3 — Done: Configuration is saved and the ESP reboots. After reboot, the dashboard loads at the ESP's IP on your local network.
The web dashboard has three tabs:
- Wi-Fi signal strength, uptime, free memory, IP address
- Bot online/offline status
- Live system logs with auto-scroll
- List of all registered commands
- Add / Edit — opens the command editor modal
- Delete — removes a command
| Field | Description |
|---|---|
| Command Trigger | Must start with / (e.g., /weather) |
| Reply Text | Message the bot sends; supports placeholders |
| API URL (optional) | REST endpoint called when command runs; supports placeholders |
| Media Type (optional) | photo, video, document, or audio |
| Media URL (optional) | Direct URL of the media file |
| Inline Buttons (optional) | Array of buttons with text and URL or callback data |
API Test — Click "Test" to call the API URL directly from the browser. The response JSON is parsed and clickable {response:...} placeholders are generated automatically.
- Timezone — UTC offset for
{time}/{date}placeholders (UTC-12 to UTC+14) - Bot Token — Change token (triggers reboot)
- Wi-Fi — Change network (reconnects immediately)
- Import Config — Upload a complete
config.jsonfile (overwrites all settings, reboots)
- Start / Stop — Pause or resume the bot
- Restart — Reboot the ESP32
- Factory Reset — Delete all settings and restart fresh
| Placeholder | Resolves to |
|---|---|
{message} |
Full incoming message text |
{sender} |
Sender's username or ID |
{first_name} |
Sender's first name |
{last_name} |
Sender's last name |
{full_name} |
first_name + " " + last_name |
{command} |
The matched command (e.g., /weather) |
{query} |
Everything after the command |
{chatid} |
Numeric chat ID |
{time} |
Current time (HH:MM:SS) — NTP-synced |
{date} |
Current date (YYYY-MM-DD) — NTP-synced |
{uptime} |
ESP32 uptime (e.g., 2d 4h 30m) |
After the bot calls an API URL, the response JSON is parsed and accessible via {response:key.path}:
// API returns:
{ "main": { "temp": 28.5 }, "weather": [{ "description": "clear sky" }] }
// Reply template:
Temp: {response:main.temp}°C, {response:weather.0.description}
// Result:
Temp: 28.5°C, clear sky- Arrays are indexed with
.0,.1, etc. - Nested objects use dot notation:
response:user.address.city
Use {eval:expression} for basic arithmetic:
{eval:{query}*2+5}
{eval:({first_name} == "admin" ? 1 : 0)}
Supports +, -, *, / and parentheses.
- API URL placeholders are replaced (before the API call)
- API is called, response JSON is fetched
{response:...}placeholders in the reply are resolved- Standard placeholders (
{first_name},{query}, etc.) and{eval:...}are resolved
Buttons support URL links and callback data:
[
{ "text": "Open Website", "url": "https://example.com" },
{ "text": "Run Command", "callback_data": "/status" }
]All button fields (text, url, callback_data) support every placeholder type including {response:...}.
When a callback button is tapped, the bot processes the callback_data as if it were a new text command — including API calls, media, and nested buttons.
Set media_type and media_url on any command to send media instead of plain text:
media_type |
Sent as |
|---|---|
photo |
sendPhoto |
video |
sendVideo |
document |
sendDocument |
audio |
sendAudio |
The reply text becomes the media caption. All placeholders work in both the URL and the caption. Buttons can be attached to media messages.
{
"wifi_ssid": "MyNetwork",
"wifi_pass": "password123",
"bot_token": "123456789:ABCdef...",
"bot_username": "MyBot",
"setup_done": true,
"timezone_offset": 420,
"commands": [
{
"cmd": "/start",
"reply": "Hello {first_name}!",
"api_url": "",
"media_url": "",
"media_type": "",
"buttons": []
}
]
}| Method | Path | Description |
|---|---|---|
| GET | /api/info |
System info (WiFi, uptime, memory, bot status) |
| GET | /api/commands |
List all commands |
| POST | /api/commands/save |
Create/update a command |
| POST | /api/commands/delete |
Delete a command |
| GET | /api/logs |
System log buffer |
| GET | /api/settings |
Get timezone, bot username |
| POST | /api/settings |
Save timezone/bot token |
| POST | /api/settings/wifi |
Change Wi-Fi credentials |
| POST | /api/settings/import |
Import full config JSON |
| POST | /api/reboot |
Reboot ESP32 |
| POST | /api/reset |
Factory reset |
| POST | /api/bot/toggle |
Start/stop the bot |
| GET | /api/wifi/scan |
Scan available networks |
| POST | /api/setup/wifi |
Setup step 1: Wi-Fi |
| GET | /api/setup/status |
Setup step 1: poll status |
| POST | /api/setup/telegram |
Setup step 2: verify token |
- PlatformIO CLI or IDE
- Python 3.7+
# Build firmware
pio run
# Upload firmware
pio run --target upload
# Upload filesystem (web UI)
pio run --target uploadfs
# Both at once
pio run --target upload --target uploadfs
# Serial monitor
pio device monitor -b 115200telesp/
├── platformio.ini # Build configuration
├── src/
│ ├── main.cpp # Entry point, WiFi, config, task spawning
│ ├── telegram.cpp # Telegram bot logic (800 lines)
│ ├── webserver.cpp # Web server handlers (410 lines)
│ ├── commands.cpp # Command CRUD operations
│ └── logging.cpp # Circular log buffer
├── include/
│ ├── config.h # Global variable declarations
│ ├── telegram.h
│ ├── webserver.h
│ ├── commands.h
│ └── logging.h
└── data/ # Web UI (uploaded to LittleFS)
├── dashboard.html
├── dashboard.js
├── setup.html
├── setup.js
└── style.css
[env:esp32-c3-devkitm-1]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
board_build.filesystem = littlefs
monitor_speed = 115200
build_flags =
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
bblanchon/ArduinoJson @ ^7.0.4- Threading: The bot runs on Core 0 as a FreeRTOS task (30KB stack). The web server runs on the main loop.
- Connection: Single persistent TLS connection to
api.telegram.org:443withkeep-alive. - Polling: Long-polling
getUpdates(timeout=3s, limit=1, offset-based). - API Calls: Per-command HTTP requests use
HTTPClientwithWiFiClientSecure(8s timeout). - Concurrency:
commands_mutexprotects the shared command cache;logMutexprotects the log buffer. - Migration: Old
/commands.jsonfiles are automatically migrated to/config.jsonon boot.
| Problem | Likely Cause | Fix |
|---|---|---|
| ESP doesn't show setup AP | Already configured | Hold reset button or trigger factory reset |
| Bot doesn't respond | Token invalid or bot paused | Check token in Settings tab; check bot_status in Info tab |
{response:...} not working |
API response too large or key path wrong | Test API from dashboard to verify response structure |
| Serial gibberish | Wrong baud rate | Use pio device monitor -b 115200 |
| Web UI not loading | Filesystem not uploaded | Run pio run --target uploadfs |
Telesp is in beta — bugs are expected, and feedback is welcome.
When reporting a bug, please include:
- ESP32 board model
- PlatformIO build logs (if applicable)
- Serial monitor output showing the error
- Steps to reproduce
- Expected vs actual behavior
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin feat/my-feature) - Open a Pull Request
Please keep code style consistent with the existing codebase (same indentation, naming conventions, etc.).
MIT