A Telegram bot that creates reminders from natural language using LLM-based text extraction.
- Natural language processing: Send any text and the bot extracts task structure (title, description, date, time) using LLM
- Smart follow-up: If information is missing, the bot asks follow-up questions
- Deadline reminders: Sends notifications 1 minute before each deadline
- Task management: Create, view, edit, and delete reminders
- Restart-safe: All tasks persist in MySQL; reminders resume after restart
- Idempotent: Duplicate messages are ignored
- Operational alerts: Optional second Telegram bot sends
warn/errorlogs; users subscribe via that bot (/start,/subscribe)
src/
├── index.js # Entry point, startup logic
├── config.js # Environment-based configuration
├── db.js # MySQL connection pool
├── models/
│ ├── task.js # Task data access (CRUD)
│ ├── conversation.js # Conversation state persistence
│ └── alert-subscriber.js # Users subscribed to system log alerts
├── services/
│ ├── llm.service.js # LLM API integration (OpenAI-compatible)
│ ├── task.service.js # Task business logic
│ ├── conversation.service.js # State management
│ └── reminder.service.js # Reminder scheduler
├── handlers/
│ ├── command.handler.js # Main bot commands (/new, /tasks, etc.)
│ ├── message.handler.js # Free text + conversation flow
│ └── alert-bot.handler.js # Second bot: subscribe/unsubscribe to log alerts
├── utils/
│ ├── logger.js # Winston logger
│ └── date.utils.js # Date/time utilities (dayjs)
└── migrations/
├── init.sql # Database schema
└── run.js # Standalone migration runner
Default: gpt-4o-mini (configurable via LLM_MODEL env var). Any OpenAI-compatible API can be used by changing LLM_BASE_URL.
The bot sends a system prompt instructing the LLM to act as a task extraction assistant, followed by the user's message with current date/time context.
The LLM returns a strict JSON object:
{
"title": "Meeting with team",
"description": "Discuss Q2 planning",
"date": "2025-03-25",
"time": "14:00"
}Fields are set to null if not determinable from the text. The application validates the response format and retries up to 3 times on failure.
| Command | Description |
|---|---|
/start |
Welcome message |
/new |
Create a new reminder |
/tasks |
List active reminders |
/task <id> |
View reminder details |
/edit <id> |
Edit a reminder |
/delete <id> |
Delete a reminder |
/cancel |
Cancel current action |
/help |
Show help |
If TELEGRAM_ALERT_BOT_TOKEN is set, the app starts a second Telegram bot (long polling) only for subscriptions and for sending warn/error alerts.
| Command | Description |
|---|---|
/start |
Subscribe to alerts (saves telegram_user_id in alert_subscribers) |
/subscribe |
Subscribe again |
/unsubscribe |
Unsubscribe |
/help |
Short help |
Use these commands in a private chat with the alert bot (not in a group).
- Create a second bot in @BotFather for alerts only. Set
TELEGRAM_ALERT_BOT_TOKENto its token (must differ from the main bot token). - In
.env, setTELEGRAM_ALERT_BROADCAST_ALL_USERS=trueto message everyone inalert_subscribers. - Optionally set
TELEGRAM_ALERT_CHAT_IDto also send every alert to one extra chat (e.g. admin DM or group). - Each user who wants alerts must open the alert bot and send
/startor/subscribeso they are stored inalert_subscribersand Telegram allows that bot to DM them.
If the user never talks to the alert bot, the API may reject DMs (403 / “bot can’t initiate conversation”).
| Field | Type | Description |
|---|---|---|
id |
INT (PK) | Auto-increment |
telegram_user_id |
BIGINT | Telegram user ID |
status |
ENUM | DRAFT, ACTIVE, DONE, CANCELED, DELETED |
title |
VARCHAR(255) | Task title |
description |
TEXT | Task description |
deadline_at |
DATETIME | Deadline in UTC |
notified |
TINYINT | Whether reminder was sent |
created_at |
DATETIME | Creation timestamp |
updated_at |
DATETIME | Last update timestamp |
| Field | Type | Description |
|---|---|---|
telegram_user_id |
BIGINT (PK) | Telegram user ID |
state |
VARCHAR(50) | Current conversation state |
payload |
JSON | State-specific data |
updated_at |
DATETIME | Last update timestamp |
Used when TELEGRAM_ALERT_BROADCAST_ALL_USERS=true. Rows are created/removed by the alert bot (/start, /subscribe, /unsubscribe).
| Field | Type | Description |
|---|---|---|
telegram_user_id |
BIGINT (PK) | Telegram user ID |
created_at |
DATETIME | First subscription time |
updated_at |
DATETIME | Last resubscribe time |
This section describes how logs flow from application code to the console and optionally to Telegram.
- Module:
src/utils/logger.js - Library: Winston
- Level:
LOG_LEVEL(defaultinfo). Messages below this level are ignored by Winston entirely (they never reach the console or Telegram).
- Every log line includes default meta:
{ service: 'reminder-bot' }. - Console transport uses a human-readable format: timestamp (in
TZ_DEFAULT/Europe/Moscowlocale), level, message, and JSON for extra fields. - In Docker, these lines are what you see in
docker compose logs -f bot.
Throughout the app, code calls logger.info, logger.warn, logger.error, etc., for example:
- Incoming Telegram messages (truncated text)
- LLM request attempts and failures
- Task CRUD and reminder sends
- Database and startup lifecycle (
Configuration validated,Reminder bot is running, …)
After the Winston logger is created, logger.warn and logger.error are wrapped:
- The original Winston method runs first → message always appears in the console (if level allows).
- Then
sendTelegramAlert(level, message, meta)runs asynchronously forwarnanderroronly.
Alerts are sent only if all of the following hold:
TELEGRAM_ALERT_BOT_TOKENis set (the alert bot, not the main bot). The same token is used for outgoingsendMessagecalls and, when set, for a second long-polling bot instance that handles/start,/subscribe, and/unsubscribe(alert-bot.handler.js).- Either
TELEGRAM_ALERT_CHAT_IDis set orTELEGRAM_ALERT_BROADCAST_ALL_USERS=true. - The log level is at least as severe as
TELEGRAM_ALERT_LEVEL(defaultwarn: bothwarnanderrorare sent; if set toerror, onlyerroris sent).
- Single chat: if
TELEGRAM_ALERT_BROADCAST_ALL_USERSis false, onlyTELEGRAM_ALERT_CHAT_IDreceives messages. - Broadcast: if
TELEGRAM_ALERT_BROADCAST_ALL_USERS=true, recipients are loaded from MySQL tablealert_subscribers(up toTELEGRAM_ALERT_MAX_RECIPIENTS, newestupdated_atfirst). IfTELEGRAM_ALERT_CHAT_IDis also set, it is included in addition to subscribers.
- The alert bot calls
https://api.telegram.org/bot<token>/sendMessagewith JSON body:chat_id,text,disable_notification(falseforerror,trueforwarn). textincludes level, message, and serializedmeta(JSON). Long text is truncated (Telegram limit).- Failures are logged at debug level on the main logger (to avoid alert loops).
A boolean isSendingAlert ensures that alert sending does not re-enter while a previous batch is still in progress (reduces risk of overlapping broadcasts).
- Local / Docker:
docker compose logs -f botor stdout ofnpm start. - Telegram: only for wrapped
warn/errorwhen alert env is configured and recipients are valid.
- Node.js 18+
- MySQL 8.0+
- Telegram Bot Token (from @BotFather)
- OpenAI API key (or compatible LLM API)
-
Clone the repository:
git clone <repo-url> cd telegram-reminder-bot
-
Install dependencies:
npm install
-
Create
.envfile from the example:cp .env.example .env
-
Fill in your secrets in
.env:TELEGRAM_BOT_TOKEN— your bot tokenLLM_API_KEY— your OpenAI API key
-
Ensure MySQL is running and create the database:
npm run migrate
-
Start the bot:
npm start
-
Create
.envfile from the example:cp .env.example .env
-
Fill in your secrets in
.env -
Start with Docker Compose:
docker compose up -d
This starts both the MySQL database and the bot. The bot waits for MySQL to be healthy before starting.
-
View logs:
docker compose logs -f bot
-
Stop:
docker compose down
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | — | Telegram Bot API token |
TELEGRAM_ALERT_BOT_TOKEN |
No | (empty) | Separate bot token for warning/error alerts |
TELEGRAM_ALERT_CHAT_ID |
No | (empty) | Telegram chat ID where alerts are sent |
TELEGRAM_ALERT_LEVEL |
No | warn |
Minimum level for alerts (warn or error) |
TELEGRAM_ALERT_BROADCAST_ALL_USERS |
No | false |
If true, send alerts to every telegram_user_id in alert_subscribers (subscribe via alert bot) |
TELEGRAM_ALERT_MAX_RECIPIENTS |
No | 500 |
Max subscribers to message per alert batch |
LLM_API_KEY |
Yes | — | LLM API key |
LLM_MODEL |
No | gpt-4o-mini |
LLM model name |
LLM_BASE_URL |
No | https://api.openai.com/v1 |
OpenAI-compatible API base URL. For ProxyAPI, use https://api.proxyapi.ru/openai/v1 with a ProxyAPI key. |
LLM_MAX_RETRIES |
No | 3 |
Max LLM retry attempts |
DB_HOST |
No | localhost |
MySQL host |
DB_PORT |
No | 3306 |
MySQL port |
DB_USER |
No | root |
MySQL user |
DB_PASSWORD |
No | (empty) | MySQL password |
DB_NAME |
No | reminder_bot |
MySQL database name |
TZ_DEFAULT |
No | Europe/Moscow |
Default timezone |
REMINDER_CHECK_INTERVAL_MS |
No | 15000 |
Reminder check interval (ms) |
LOG_LEVEL |
No | info |
Log level (error, warn, info, debug) |
For automated deployment on a local VM, a GitHub Actions workflow or a simple Git hook can be used:
-
On the VM, set up a bare Git repository:
mkdir -p /opt/reminder-bot.git cd /opt/reminder-bot.git git init --bare -
Create a
post-receivehook (/opt/reminder-bot.git/hooks/post-receive):#!/bin/bash TARGET="/opt/reminder-bot/app" GIT_DIR="/opt/reminder-bot.git" git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f main cd $TARGET npm ci --omit=dev docker compose down docker compose up -d --build
-
Make it executable:
chmod +x /opt/reminder-bot.git/hooks/post-receive
-
Add the VM as a remote on your development machine:
git remote add deploy ssh://user@vm-ip/opt/reminder-bot.git
-
Push to deploy:
git push deploy main
No manual intervention required after the initial setup.
MIT