An intelligent note-taking system that processes Telegram messages using AI to automatically categorize, tag, and organize notes. Users send messages to a Telegram bot, which are processed by OpenAI to extract structured information and stored in a Supabase database.
āāāāāāāāāāāāāāā
ā Telegram ā
ā Bot ā
āāāāāāāā¬āāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Webhook (HTTP) ā ā Receives messages
ā Val.town Val ā ā Validates & stores in SQLite
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāā
ā SQLite Queue ā ā Temporary message queue
āāāāāāāāāā¬āāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāā
ā Worker (Interval) ā ā Processes one message at a time
ā Val.town Val ā
āāāāāāāā¬āāāāāāāāāāāāāāā
ā
āāāāāāāāāāāāāāā
ā¼ ā¼
āāāāāāāāāāāā āāāāāāāāāāāāāāā
ā OpenAI ā ā Supabase ā
ā API ā ā PostgreSQL ā
āāāāāāāāāāāā āāāāāāāāāāāāāāā
ā ā
āāāāāāāā¬āāāāāāā
ā¼
āāāāāāāāāāāāāāā
ā Telegram ā ā Confirmation message
ā User ā
āāāāāāāāāāāāāāā
Telegram Webhook (HTTP Val)
Message Processor Worker (Interval Val)
SQLite Queue
Supabase Database
Stores Telegram users mapped to app users.
- id (UUID, PK)
- telegram_user_id (BIGINT, UNIQUE)
- username (TEXT)
- first_name (TEXT)
- last_name (TEXT)
- created_at (TIMESTAMP)
- updated_at (TIMESTAMP)
User-specific tags with visual properties.
- id (UUID, PK)
- user_id (UUID, FK ā users)
- name (TEXT)
- color (TEXT) - hex color
- icon (TEXT) - emoji
- created_at (TIMESTAMP)
- updated_at (TIMESTAMP)
- UNIQUE(user_id, name)
Core table storing all processed notes.
- id (UUID, PK)
- user_id (UUID, FK ā users)
- content (TEXT) - original message
- title (TEXT) - AI-generated title
- note_type (ENUM: note, reminder, event, link, reference, task, idea)
- priority (ENUM: low, medium, high, urgent)
- status (ENUM: active, completed, archived, deleted)
- created_at (TIMESTAMP)
- updated_at (TIMESTAMP)
- reminder_at (TIMESTAMP) - for reminders
- event_date (TIMESTAMP) - for events
- completed_at (TIMESTAMP) - for tasks
- telegram_message_id (BIGINT)
- url (TEXT) - extracted URL
- location (TEXT) - for events
- search_vector (TSVECTOR) - full-text search
Many-to-many relationship between notes and tags.
- note_id (UUID, FK ā notes)
- tag_id (UUID, FK ā tags)
- created_at (TIMESTAMP)
- PRIMARY KEY (note_id, tag_id)
Stores detailed AI processing results.
- id (UUID, PK)
- note_id (UUID, FK ā notes)
- raw_response (JSONB) - full AI response
- extracted_title (TEXT)
- extracted_tags (TEXT[])
- extracted_type (note_type)
- extracted_priority (note_priority)
- extracted_reminder_date (TIMESTAMP)
- extracted_event_date (TIMESTAMP)
- extracted_url (TEXT)
- extracted_location (TEXT)
- sentiment (TEXT: positive, negative, neutral)
- confidence_score (DECIMAL)
- model_used (TEXT)
- processing_time_ms (INTEGER)
- created_at (TIMESTAMP)
- error (TEXT)
- success (BOOLEAN)
Processing queue in Supabase (alternative to SQLite).
- id (UUID, PK)
- user_id (UUID, FK ā users)
- telegram_message_id (BIGINT)
- chat_id (BIGINT)
- text (TEXT)
- processed_at (TIMESTAMP)
- created_at (TIMESTAMP)
- note_id (UUID, FK ā notes)
- processing_error (TEXT)
- retry_count (INTEGER)
Temporary queue for incoming Telegram messages.
- id (INTEGER, PK, AUTOINCREMENT)
- chat_id (INTEGER)
- username (TEXT)
- text (TEXT)
- timestamp (INTEGER)
- created_at (DATETIME)
- processed_at (DATETIME) - NULL when unprocessed
Raw Telegram message text from user.
The AI analyzes messages to extract:
| Input Message | AI Output |
|---|---|
| "Buy milk tomorrow" | type: task, reminder_date: [tomorrow], tags: ["shopping"], priority: medium |
| "Meeting with John at 3pm Friday" | type: event, event_date: [Friday 3pm], tags: ["meeting"], priority: medium |
| "https://example.com interesting article" | type: link, url: "https://example.com", tags: ["article"], priority: low |
| "URGENT: Fix production bug" | type: task, priority: urgent, tags: ["work", "bug"], sentiment: negative |
{ "title": "Buy milk", "type": "task", "priority": "medium", "tags": ["shopping", "groceries"], "reminder_date": "2026-01-22T00:00:00Z", "event_date": null, "url": null, "location": null, "sentiment": "neutral", "confidence": 0.92 }
// Key features:
- POST-only endpoint
- Validates X-Telegram-Bot-Api-Secret-Token
- Stores messages in SQLite with processed_at = NULL
- Returns 200 OK immediately
- Logs all activity
// Key features:
- Interval-based execution
- Processes one message at a time (LIMIT 1)
- FIFO processing (ORDER BY created_at ASC)
- OpenAI integration for analysis
- Supabase CRUD operations
- User creation/lookup
- Tag management
- Confirmation messages to users
- Error handling
- Processing time tracking
response_format: { type: "json_object" }| Variable | Description | Where to Get |
|---|---|---|
TELEGRAM_BOT_TOKEN | Bot authentication token | @BotFather on Telegram |
TELEGRAM_WEBHOOK_SECRET | Custom secret for webhook validation | Generate random string |
SUPABASE_URL | Supabase project URL | Supabase dashboard |
SUPABASE_SERVICE_KEY | Service role key (bypasses RLS) | Supabase Settings ā API |
OPENAI_API_KEY | OpenAI API key | OpenAI platform |
# Message @BotFather on Telegram /newbot # Follow prompts, save token
1. Go to https://supabase.com 2. Create new project 3. Run the database schema SQL 4. Get URL and service key from Settings ā API
1. Create new HTTP Val on Val.town
2. Paste webhook code
3. Add environment variables
4. Get Val URL (https://username-valname.web.val.run)
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-val.web.val.run", "secret_token": "your-webhook-secret" }'
1. Create new Interval Val on Val.town
2. Paste worker code
3. Add environment variables
4. Set interval (recommended: 10-30 seconds)
Send a message to your bot on Telegram
Check Val.town logs
Verify data in Supabase
/list - Show recent notes/search <query> - Search notes/tags - Manage tags/reminders - View upcoming reminders/stats - Usage statistics/settings - User preferencesUser sends message ā Telegram Bot
ā
Webhook receives
ā
Validates secret
ā
Stores in SQLite queue
ā
Returns 200 OK
ā
[Message in queue: processed_at = NULL]
ā
Worker runs (interval trigger)
ā
Fetches oldest unprocessed message
ā
Sends to OpenAI for analysis
ā
OpenAI returns structured JSON
ā
Get/Create user in Supabase
ā
Insert note into Supabase
ā
Insert AI results into Supabase
ā
Get/Create tags and link to note
ā
Format confirmation message with emojis
ā
Send confirmation back to Telegram
ā
Mark message as processed (SQLite)
ā
Worker completes
ā
[Next interval: process next message]
vals/
āāā telegram-webhook.ts # HTTP endpoint for receiving messages
āāā telegram-worker.ts # Interval worker for processing
project/
āāā telegram-notes-bot-summary.md # This document
āāā database-schema.sql # Supabase schema
āāā webhook.ts # Webhook implementation
āāā worker.ts # Worker implementation
Solution: Check worker logs, verify OpenAI API key, check Supabase connection
Solution: Ensure processed_at is set correctly, check for worker conflicts
Solution: Verify "json" is in system prompt, handle parsing errors
Solution: Check user_id matching, verify tag creation logic
Solution: Verify BOT_TOKEN, check sendMessage error handling
User: "Buy milk tomorrow"
ā
AI Analysis:
- Type: task
- Priority: medium
- Tags: ["shopping"]
- Reminder: tomorrow 9am
ā
Stored in database
ā
User receives: "ā
Task: Buy milk
Priority: š” medium
Tags: #shopping
Reminder: Jan 22, 2026 9:00 AM"
User: "Team meeting Friday 2pm with John and Sarah"
ā
AI Analysis:
- Type: event
- Priority: medium
- Tags: ["meeting", "work"]
- Event date: Friday 2pm
ā
Stored in database
ā
User receives: "š
Event: Team meeting Friday 2pm with John and Sarah
Event: Jan 24, 2026 2:00 PM
Tags: #meeting #work"
User: "https://blog.example.com/ai-trends great article about AI"
ā
AI Analysis:
- Type: link
- Priority: low
- Tags: ["article", "ai"]
- URL: https://blog.example.com/ai-trends
ā
Stored in database
ā
User receives: "š Link: great article about AI
URL: https://blog.example.com/ai-trends
Tags: #article #ai"
Last Updated: January 2026 Version: 0.1.0 Status: MVP - Active Development