Telegram Bot
The NIVAA Telegram bot (@NIVAAMainBot) provides real-time session management, daily reminders, check-in/out, and coordinator tools. It connects to the same D1 database as the web platform via webhook.
The bot runs as a webhook handler inside the Cloudflare Worker. Telegram sends updates to /telegram/webhook, and the Worker processes commands and callback queries synchronously.
Account Linking
Telegram accounts are linked to NIVAA users via deep links. Each user gets a unique link that associates their Telegram chat ID with their NIVAA user ID and role.
How Deep Links Work
The deep link format is:
https://t.me/NIVAAMainBot?start=link_ROLE_ID
Examples:
https://t.me/NIVAAMainBot?start=link_TUTOR_1052
https://t.me/NIVAAMainBot?start=link_ADMIN_20When a user clicks this link, Telegram opens a chat with the bot and sends /start link_ROLE_ID. The bot then:
- 1 Parses the role and user ID from the deep link payload.
- 2 Verifies the user exists in the
userstable. - 3 Creates or updates a record in
telegram_links, mapping the Telegram chat ID to the NIVAA user. - 4 Responds with a confirmation message including the user's display name and role.
Auto-generated Links
When coordinators add new users via /admin/people, the system automatically generates the Telegram deep link. This link is displayed on the user profile and can be shared directly.
Tutor Commands
Tutors have access to the following commands after linking their account:
| Command | Description | Detail |
|---|---|---|
/sessions | Today's sessions | Shows all sessions for today with check-in/out status, student names, addresses (with Google Maps links), and inline buttons for check-in, check-out, and session changes. |
/upcoming | Next 7 days | Lists all scheduled sessions for the next week, grouped by date. |
/checkin | Check in | Checks into the next unchecked session today. Respects the 30-minute-before-start time window. Also available as an inline button on <code>/sessions</code>. |
/checkout | Check out | Checks out of the currently active session. Available from 30 minutes before scheduled end until 24 hours after. Transitions status to <code>Awaiting_Approval_Parent</code> and prompts for session notes. |
/todos | Daily to-do list | Shows pending items: sessions to attend, notes to upload, admin tasks. |
/mystudents | Active matches | Lists all students the tutor is currently matched with, including subjects and areas. |
/postings | Browse postings | Shows open postings the tutor can apply to, with subject, location, and schedule details. |
/notes | Submit session notes | Allows the tutor to send text, photos, documents, or voice messages as session notes. Files are downloaded from Telegram and stored in R2 (max 10MB per file). |
/mypay | Billing summary | Shows the current month's session count, hours worked, and sessions pending parent approval. |
/help | Command list | Displays all available commands for the user's role. |
Coordinator Commands
Coordinators (Admin role) have an expanded command set:
| Command | Description | Detail |
|---|---|---|
/dashboard | System KPIs | Platform-wide metrics: students, educators, today's sessions, open postings, open tickets, disputes. |
/sessions | All sessions today | Lists all sessions across all tutors for the current day, showing tutor names, student names, and check-in status. Limited to 20 results. |
/alerts | Open tickets | Lists open support tickets (status Open or In_Progress), showing category and subject. |
/pending | Sessions awaiting approval | Lists all sessions in <code>Awaiting_Approval_Parent</code> status that need parent confirmation. |
/postingsqueue | Posting pipeline | Summary of the posting funnel: open postings, applications received, pending coordinator review. |
/student <name|id> | Student lookup | Fuzzy search by name or exact lookup by ID. Returns child profile, parent info, and active matches. |
/tutor <name|id> | Tutor lookup | Fuzzy search by name or exact lookup by ID. Returns tutor profile, active matches, and session stats. |
/lowpackages | Low balance parents | Lists parents whose session packages are below 20% remaining. |
/newapps | Recent applications | Shows the most recent posting applications that need coordinator review. |
/impersonate | View as tutor | Without arguments: shows a list of tutors with inline buttons. With ID: <code>/impersonate 42</code> sets impersonation. While impersonating, tutor commands (<code>/sessions</code>, <code>/todos</code>, <code>/upcoming</code>) show that tutor's data. |
/stopimpersonate | Return to coordinator view | Clears the impersonation flag and returns to the coordinator's own view. |
Session Change / Cancel Flow
Tutors can cancel or reschedule sessions directly from Telegram. The flow includes a mandatory parent acknowledgement step and automatic coordinator notification.
Cancel Flow
Reschedule Flow
Callback Data Encoding
Telegram inline buttons use callback data strings to encode the action and context. The encoding scheme:
chg_ID → cnl_ID chg_ID → rsc_ID cnl_ID → cnl_c_ID cnl_c_ID → cnl_y_ID cnl_c_ID → cnl_n_ID rsc_ID → rsc_rN_ID rsc_rN_ID → rsc_yN_ID rsc_rN_ID → rsc_nN_IDCron Reminders
Daily 7am SGT Digest
Every morning at 7:00 AM SGT, each linked tutor receives a digest message containing:
- Greeting with display name
- Today's session count and schedule (times, student names, addresses with Google Maps links)
- Count of pending session notes that need uploading
Pre-session 1-hour Reminders
Approximately 1 hour before each session, the tutor receives a reminder with:
- Session time and student name
- Address with Google Maps link
- "Check In Now" inline button (if the 30-minute check-in window is already open)
The cron runs every 15 minutes and catches sessions starting in the 45-75 minute window to account for the cron interval.
Deduplication
Both reminder types are deduplicated via the telegram_reminder_log table. The daily digest deduplicates based on chat_id + reminder_type='daily_7am' with a 20-hour lookback. Pre-session reminders deduplicate on chat_id + reminder_type='pre_session_1hr' + appointment_id.
Database Tables
telegram_links
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment ID |
user_id | INTEGER | FK to users.id |
chat_id | TEXT UNIQUE | Telegram chat ID (unique constraint prevents duplicate links) |
role | TEXT | Tutor | Admin |
impersonating_tutor_id | INTEGER NULL | If set, coordinator is viewing bot as this tutor (migration 0022) |
linked_at | DATETIME | When the link was created/updated |
telegram_reminder_log
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment ID |
chat_id | TEXT | Telegram chat ID of recipient |
reminder_type | TEXT | daily_7am | pre_session_1hr |
appointment_id | INTEGER NULL | NULL for daily digest, set for pre-session reminders |
sent_at | DATETIME | When the reminder was sent (used for dedup lookback) |
cancellation_requests
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment ID |
appointment_id | INTEGER | FK to appointments.id |
tutor_user_id | INTEGER | FK to users.id |
parent_acknowledged | BOOLEAN | Whether parent was informed before cancellation |
coordinator_notified | BOOLEAN | Whether coordinators were notified |
reschedule_requests
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment ID |
appointment_id | INTEGER | FK to appointments.id |
proposed_by | TEXT | Tutor | Parent | Admin |
proposed_by_user_id | INTEGER | FK to users.id |
new_date | TEXT | Proposed new date (initially same as original, updated by coordinator) |
new_start_time | TEXT | Proposed new start time |
new_end_time | TEXT | Proposed new end time |
status | TEXT | Proposed | Accepted | Rejected |
reason | TEXT | Schedule conflict / Student unavailable / Other |
source_channel | TEXT | telegram | web |
parent_acknowledged | BOOLEAN | Whether parent was informed |