I wanted to build a small AI project that was more than:
prompt → LLM → response
So I built an AI-powered Telegram motivation bot using Node.js, Google Gemini and the Telegram Bot API.
The interesting part isn't the motivational content itself.
The interesting part is everything around the AI model:
- Scheduling
- Personalization
- Persistent user state
- Streaks
- Feedback
- Telemetry
- Retry logic
- Model fallback
- Caching
- Local fallback content
- Protected webhooks
This article breaks down the architecture.
Tech Stack
The core stack is:
Node.js
Express.js
Telegram Bot API
Google Gemini
node-cron
dotenv
Jest
The project uses ES modules and keeps the application separated into components such as bot commands, actions, services, data management and configuration.
High-Level Architecture
The system roughly looks like this:
┌─────────────────┐
│ Telegram User │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Telegram Bot │
│ Command Layer │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Motivation │
│ Generation │
│ Service │
└────────┬────────┘
│
┌────────┴────────┐
▼ ▼
┌──────────┐ ┌───────────┐
│ Gemini │ │ Fallback │
│ API │ │ System │
└──────────┘ └───────────┘
│
▼
┌────────────┐
│ Telemetry │
└────────────┘
1. Telegram Command Layer
The bot exposes multiple commands.
Some of the main ones are:
/motivate
/today
/history
/stats
/leaderboard
/settings
/subscribe
/unsubscribe
/schedule
/set_tone
/set_language
/suggest_quote_topic
For example:
/motivate
generates an on-demand motivational message.
A user can also request a topic-specific message:
/suggest_quote_topic job interviews
The command layer initializes the user, invokes the generation service and stores the resulting data.
2. AI Generation Service
The main generation logic lives in the motivation/brain service.
The service uses the Google Generative AI SDK.
One interesting design decision is the use of multiple persona archetypes.
const personas = {
stoic: "...",
warrior: "...",
philosopher: "...",
strategist: "...",
mentor: "...",
elder: "...",
survivor: "...",
observer: "...",
wanderer: "..."
};
If the user selects a mixed/random mode, the application can choose an archetype dynamically.
3. Multiple Generation Modalities
The application also varies how the generated message should be expressed.
The current modalities include:
RAW_REALITY
DEEP_OBSERVATION
QUIET_COMPASSION
PRAGMATIC
This is useful because prompt variation doesn't necessarily require completely different application architectures.
A small controlled set of generation dimensions can create considerably more variety.
4. Output Constraints
The prompt also places constraints on the generated content.
For example, the generation service asks the model for:
- A short response
- A complete standalone sentence
- No unnecessary formatting
- No generic AI clichés
- A grounded tone
The application then trims and validates the model output before returning it.
5. Retry and Model Fallback
This is one of the parts I found most useful from an engineering perspective.
The application maintains a model priority chain.
Conceptually:
const MODEL_PRIORITY_CHAIN = [
"primary-model",
"fallback-model"
];
The generation function loops through the configured models and retries failures.
It also detects certain model availability/deprecation errors and can move to the next model.
This is important because AI providers change models over time.
Hard-coding one model and assuming it will always exist isn't a great long-term strategy.
6. Memory Cache
The generation service also keeps a temporary in-memory cache.
The cache is keyed around parameters such as:
language
tone
schedule
A/B variant
This means the application has another way to avoid unnecessary AI calls when an appropriate recent response already exists.
The cache also becomes useful when dealing with API rate limits or temporary upstream failures.
7. Local Fallback
What happens if the AI system fails completely?
The application can retrieve fallback content from local data.
The conceptual hierarchy is:
Gemini
↓
Retry
↓
Model fallback
↓
Memory cache
↓
Local fallback
This is a useful pattern for any application that depends on an external API.
A graceful degradation strategy is usually better than returning an error every time an external service has a problem.
8. Automated Scheduling
The bot uses node-cron.
The current schedules include:
cron.schedule('0 8 * * *', ...)
cron.schedule('0 13 * * *', ...)
cron.schedule('0 18 * * *', ...)
cron.schedule('0 19 * * 0', ...)
These correspond to:
08:00 → Morning
13:00 → Midday
18:00 → Evening
Sunday 19:00 → Weekly
The schedules use:
Asia/Kolkata
as the timezone.
The application also has an automated cleanup job.
9. Per-User Scheduling
The cron system doesn't simply broadcast every message to everyone.
It checks each user's schedule configuration.
For example:
user.schedule[scheduleType] === true
Only users who have enabled a particular dispatch period are targeted.
That creates an important separation:
Global Scheduler
↓
User Preferences
↓
Eligible Subscribers
↓
AI Generation
↓
Telegram Delivery
10. Streak Tracking
After successful dispatch, the user's streak is updated.
The project also has milestone badges.
For example:
3 days → Rising Star
7 days → 7-Day Believer
14 days → Unbreakable
30 days → Iron Will
100 days → Century Member
This turns a simple messaging bot into a stateful application.
11. Feedback Buttons
Generated messages contain inline Telegram buttons:
👍 👎
The callback handler determines whether the user voted up or down.
The application then records:
chat ID
quote
vote
A/B variant
The buttons are subsequently removed to prevent repeated submissions for the same message.
This is a small feature, but it introduces an important concept:
AI output can be treated as an experiment rather than an immutable result.
12. A/B Variant
The project also has an A/B mechanism.
The user's chat ID is used to determine the variant.
One variant receives an additional generation instruction.
This creates a simple way to compare different prompting strategies.
In a more advanced implementation, this could be replaced with a proper experiment assignment system.
13. Telemetry
The application records telemetry around generated messages.
Some tracked information includes:
event
chatId
schedulePeriod
quoteText
source
responseTimeMs
apiSuccess
This makes it possible to answer questions such as:
- How long is AI generation taking?
- How often is the API succeeding?
- How frequently is fallback content being used?
- Which scheduling period generated an event?
- Which model produced the response?
Without telemetry, these questions are much harder to answer.
14. Express API
The application also starts an Express server.
One endpoint exposes recent generated quotes:
GET /api/quotes/latest
There is also a protected broadcast endpoint:
POST /api/webhook/broadcast
The broadcast endpoint checks an API key supplied through a request header.
It can then initiate a broadcast to active users.
This creates an integration point for external systems.
15. Environment Variables
Secrets are kept outside the source code.
The application expects configuration such as:
PORT
TELEGRAM_BOT_API_TOKEN
GEMINI_API_KEY
TELEGRAM_CHAT_ID
WEBHOOK_SECRET
IS_TEST_MODE
This is particularly important for Telegram and AI projects because both bot tokens and API keys should never be committed to a public repository.
16. Local Test Mode
The application also has a test mode.
Instead of waiting for the scheduled cron execution, the system can execute the dispatch logic immediately.
That makes local development much easier.
A scheduled system without a test path can become frustrating to debug.
17. Why This Project Is More Than an AI Demo
The project started with a very simple feature:
Generate a motivational message.
But the resulting architecture looks more like:
┌───────────────┐
│ Telegram │
└───────┬───────┘
│
┌───────▼───────┐
│ Commands │
└───────┬───────┘
│
┌──────────▼──────────┐
│ User State │
│ Preferences │
│ Schedule │
│ Streak │
└──────────┬──────────┘
│
┌───────▼───────┐
│ AI Engine │
└───────┬───────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Gemini Cache Fallback
│ │ │
└─────────────┼─────────────┘
▼
Telemetry
│
▼
Feedback Loop
And that's what makes the project useful as a learning exercise.
Lessons From Building It
The biggest lesson was that:
Calling an LLM is the easy part.
The engineering challenges appear around it.
You need to think about:
- Reliability
- Rate limits
- Model retirement
- Caching
- User state
- Scheduling
- Feedback
- Observability
- Security
- Testing
- Graceful degradation
Those problems are not unique to motivational bots.
They appear in many AI applications.
Possible Improvements
If I continued developing this project, I'd consider adding:
- Redis for distributed caching
- PostgreSQL or MongoDB for persistent production storage
- Docker
- More comprehensive automated tests
- Structured logging
- Metrics dashboards
- More sophisticated experimentation
- User-specific scheduling/timezones
- Better analytics
- Additional messaging integrations
Conclusion
This project started as a small Telegram automation idea.
It ended up becoming an interesting exercise in combining:
Node.js + Telegram + Gemini + automation + state + telemetry + reliability.
If you're learning AI application development, don't stop at:
const result = await model.generateContent(prompt);
Ask what happens before and after that line.
That's where most of the interesting engineering begins.
Source Code
The complete project is available here:
https://github.com/starJeet000/Telegram-Daily-Motivation-Bot
If you build something similar, I'd be interested in seeing how you handle AI reliability, caching and feedback.
Top comments (0)