\n\n\n\n I Built a Telegram Bot That Schedules My Messages - AI7Bot \n

I Built a Telegram Bot That Schedules My Messages

📖 12 min read2,270 wordsUpdated Mar 26, 2026

Hey everyone, Marcus here from ai7bot.com. Today, I want to talk about something that’s been buzzing in my personal dev projects lately: Telegram bots. Specifically, I want to explore how to make your Telegram bot not just a responder, but a scheduler. Forget the simple “hello world” stuff; we’re going to build a bot that remembers things for you and sends messages at specific times. Think of it as your personal, silent digital assistant, living right inside your Telegram chats.

I remember a few months ago, I was trying to coordinate a weekly D&D session with my group. We kept forgetting who was hosting, what time we agreed on, and what snacks everyone was bringing. We tried shared notes, calendars, everything. But the common denominator was always Telegram. That’s when it hit me: why not make a bot that just reminds us? Not just a one-off reminder, but something recurring, something that adapts. That little frustration led me down a rabbit hole, and what I found was surprisingly straightforward to implement once you get past the initial setup.

Beyond Simple Replies: The Power of Scheduled Messages

Most basic Telegram bots are reactive. You send them a command, they send back a reply. That’s great for simple queries, but it doesn’t really tap into the full potential of a bot. The real magic happens when your bot can be proactive – when it can initiate conversations, send updates, or remind you about something without you having to poke it first. This is where scheduled messages come in.

Imagine a bot that:

  • Reminds your team about daily stand-ups five minutes before they start.
  • Sends you a personalized weather forecast every morning at 7 AM.
  • Notifies you about important deadlines for a project.
  • Pings your D&D group every Sunday at 3 PM with a “session starting soon!” message.

The possibilities are endless. And the best part? It’s not as complicated as you might think. We’ll be using Python, a popular language for bot development, and a couple of trusty libraries.

What We’ll Need: The Essentials

Before we jump into the code, let’s list our tools:

  1. Python 3: If you don’t have it, go grab it. It’s the backbone of our bot.
  2. python-telegram-bot library: This is our main interface with the Telegram Bot API. It handles all the nitty-gritty communication.
  3. APScheduler library: This is the star of the show for scheduling. It lets us run functions at specific times or intervals.

You can install the Python libraries using pip:


pip install python-telegram-bot APScheduler

And, of course, you’ll need a Telegram bot token. If you haven’t created a bot yet, head over to BotFather on Telegram, send him /newbot, and follow the instructions. He’ll give you a token – keep it safe!

Setting Up the Basic Bot Structure

First, let’s get our basic Telegram bot up and running. This part should be familiar if you’ve done any bot development before.


from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

# Replace with your actual bot token from BotFather
BOT_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
 """Sends a greeting message when the /start command is issued."""
 await update.message.reply_text("Hello! I'm your scheduling bot. Use /help to see what I can do.")

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
 """Sends a help message."""
 await update.message.reply_text("I can schedule messages for you. Try /schedule to set a reminder!")

def main() -> None:
 """Starts the bot."""
 application = Application.builder().token(BOT_TOKEN).build()

 application.add_handler(CommandHandler("start", start))
 application.add_handler(CommandHandler("help", help_command))

 print("Bot started and listening...")
 application.run_polling(allowed_updates=Update.ALL_TYPES)

if __name__ == "__main__":
 main()

Run this script, and if you send /start or /help to your bot, you should get a response. This is our foundation. Now, let’s bring in the scheduler.

Introducing APScheduler: Your Timekeeper

APScheduler stands for “Advanced Python Scheduler.” It’s a solid library that allows you to schedule Python functions to run at specific times, dates, or intervals. We’ll be using its BlockingScheduler, which runs in the main thread of our application.

The core idea is this: when a user tells our bot to schedule something, we’ll store that request and then use APScheduler to add a job. When the time comes, APScheduler will execute a function that sends the message to the user.

Scheduling Our First Message

Let’s modify our bot to include scheduling capabilities. We’ll start with a simple command, /schedule, which will take a message and a time (e.g., “in 5 minutes”).


from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime, timedelta
import asyncio

# Replace with your actual bot token
BOT_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"

# Initialize the scheduler
scheduler = BlockingScheduler()

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
 """Sends a greeting message."""
 await update.message.reply_text("Hello! I'm your scheduling bot. Use /help to see what I can do.")

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
 """Sends a help message."""
 await update.message.reply_text(
 "I can schedule messages for you. Try /schedule <minutes> <your message>.\n"
 "Example: /schedule 5 Remember to buy groceries!"
 )

async def send_scheduled_message(chat_id: int, message_text: str, application: Application) -> None:
 """Sends the actual scheduled message."""
 # We need to get the bot instance to send messages outside of an Update context
 await application.bot.send_message(chat_id=chat_id, text=message_text)
 print(f"Sent scheduled message to {chat_id}: {message_text}")

async def schedule_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
 """Schedules a message."""
 if not context.args or len(context.args) < 2:
 await update.message.reply_text(
 "Usage: /schedule <minutes> <your message>.\n"
 "Example: /schedule 5 Remember to buy groceries!"
 )
 return

 try:
 minutes = int(context.args[0])
 if minutes <= 0:
 await update.message.reply_text("Minutes must be a positive number.")
 return
 
 message_to_schedule = " ".join(context.args[1:])
 
 # Calculate the future time
 run_time = datetime.now() + timedelta(minutes=minutes)
 
 # Add the job to the scheduler
 # We need to pass the application instance so send_scheduled_message can access it
 scheduler.add_job(
 send_scheduled_message, 
 'date', 
 run_date=run_time, 
 args=[update.effective_chat.id, message_to_schedule, context.application]
 )
 
 await update.message.reply_text(
 f"Okay, I'll remind you in {minutes} minute(s) at {run_time.strftime('%H:%M:%S')}!"
 )
 print(f"Scheduled job for {update.effective_chat.id} at {run_time} with message: {message_to_schedule}")

 except ValueError:
 await update.message.reply_text("Please provide a valid number for minutes.")
 except Exception as e:
 await update.message.reply_text(f"Something went wrong: {e}")

def main() -> None:
 """Starts the bot and the scheduler."""
 application = Application.builder().token(BOT_TOKEN).build()

 application.add_handler(CommandHandler("start", start))
 application.add_handler(CommandHandler("help", help_command))
 application.add_handler(CommandHandler("schedule", schedule_command))

 # Start the scheduler in a separate thread or process if you need non-blocking behavior
 # For simplicity, we'll run it in the main thread with run_polling.
 # APScheduler's BlockingScheduler blocks, so we need to run it concurrently.
 # A common pattern is to run the scheduler in a separate thread.
 
 # Let's start the scheduler in a non-blocking way for production use
 # by using BackgroundScheduler or by putting BlockingScheduler in a separate thread.
 # For this example, we will slightly adjust how main runs.
 
 # APScheduler's BlockingScheduler will block the main thread.
 # To make it work with python-telegram-bot's application.run_polling(),
 # we need to ensure the scheduler runs without blocking the bot's updates.
 # A simple way to do this is to use a BackgroundScheduler.

 from apscheduler.schedulers.background import BackgroundScheduler
 global scheduler # Re-initialize scheduler for BackgroundScheduler
 scheduler = BackgroundScheduler()
 scheduler.start()

 print("Bot started and listening...")
 application.run_polling(allowed_updates=Update.ALL_TYPES)
 
 # Shutdown the scheduler when the bot stops
 scheduler.shutdown()

if __name__ == "__main__":
 main()

A quick note on BlockingScheduler vs. BackgroundScheduler: In my initial testing for this article, I quickly realized that BlockingScheduler (as the name implies) blocks the main thread. This means your bot wouldn’t be able to receive new updates from Telegram while the scheduler is running. Not ideal! The fix is to use BackgroundScheduler, which runs in its own thread, allowing your bot to remain responsive. I’ve updated the main() function in the snippet above to reflect this.

Now, if you run this updated script, you can send your bot something like /schedule 1 Test reminder! and after one minute, your bot should send you “Test reminder!”. How cool is that?

Making it More Flexible: Recurring Jobs

One-off reminders are good, but what about recurring events? APScheduler makes this incredibly easy with its interval and cron triggers. Let’s add a command to schedule a message every X minutes.


# ... (previous imports and initializations) ...

async def schedule_recurring_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
 """Schedules a recurring message."""
 if not context.args or len(context.args) < 2:
 await update.message.reply_text(
 "Usage: /schedule_every <minutes> <your message>.\n"
 "Example: /schedule_every 60 Hourly check-in!"
 )
 return

 try:
 minutes_interval = int(context.args[0])
 if minutes_interval <= 0:
 await update.message.reply_text("Minutes must be a positive number.")
 return
 
 message_to_schedule = " ".join(context.args[1:])
 chat_id = update.effective_chat.id
 
 # Add the recurring job to the scheduler
 job_id = f"recurring_job_{chat_id}_{datetime.now().timestamp()}" # Unique ID for potential removal
 scheduler.add_job(
 send_scheduled_message, 
 'interval', 
 minutes=minutes_interval, 
 args=[chat_id, message_to_schedule, context.application],
 id=job_id,
 replace_existing=True # Optional: replace if ID exists
 )
 
 await update.message.reply_text(
 f"Okay, I'll remind you every {minutes_interval} minute(s) with: '{message_to_schedule}'"
 )
 print(f"Scheduled recurring job '{job_id}' for {chat_id} every {minutes_interval} minutes.")

 except ValueError:
 await update.message.reply_text("Please provide a valid number for minutes.")
 except Exception as e:
 await update.message.reply_text(f"Something went wrong: {e}")

# ... (add this handler in main() function) ...
# application.add_handler(CommandHandler("schedule_every", schedule_recurring_command))

Remember to add the new command handler in your main() function:


def main() -> None:
 # ... existing handlers ...
 application.add_handler(CommandHandler("schedule_every", schedule_recurring_command))
 # ... rest of main() ...

Now you can tell your bot /schedule_every 15 Don't forget to stretch! and it will dutifully remind you every 15 minutes. This is where the D&D session reminder really clicked for me. I could set it once, and it would just work every week.

Handling State and Persistence (A Necessary Next Step)

Right now, if you restart your bot, all scheduled jobs are lost. That’s not great for a reliable scheduler. For a real-world bot, you’d want to store these scheduled jobs in a database (like SQLite, PostgreSQL, or even a simple JSON file) and reload them when the bot starts up. APScheduler has built-in support for job stores, which makes this relatively straightforward.

For a production bot, I’d strongly recommend adding a job store. Here’s a conceptual outline of how you’d integrate it with SQLite:


from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

# ... (rest of your imports and code) ...

# In your main() function:
def main() -> None:
 # ... existing application setup ...

 jobstores = {
 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
 }
 
 # Re-initialize scheduler with job stores
 global scheduler
 scheduler = BackgroundScheduler(jobstores=jobstores)
 scheduler.start()

 # ... rest of main() ...

When using a job store, APScheduler handles the serialization and deserialization of jobs. This means even if your bot crashes or restarts, it will pick up all its old jobs right where it left off. This is crucial for any serious application.

Actionable Takeaways for Your Own Bot

Building a bot that can schedule messages opens up a whole new world of possibilities. Here’s what I’ve learned and what you should consider:

  1. Start Simple: Don’t try to build the most complex scheduler right away. Get a basic one-off reminder working first, then add recurring jobs.
  2. Error Handling is Key: Users will inevitably enter wrong commands or invalid times. Your bot needs to be solid enough to handle these gracefully. Add try-except blocks!
  3. Persistence is Non-Negotiable: For any bot that you expect to run for more than a few minutes, you absolutely need to store scheduled jobs. APScheduler‘s job stores make this easy. SQLite is a great starting point for local storage.
  4. User Feedback: Always confirm with the user that their schedule was set. “Okay, I’ll remind you in 5 minutes!” goes a long way.
  5. Clear Instructions: Provide good /help messages with examples. It helps users understand how to interact with your bot.
  6. Consider Timezones: For a global bot, timezones can be a nightmare. APScheduler can handle them, but it adds complexity. For a personal bot, you might be able to get away with just using UTC or the server’s local time.

This journey from a simple D&D reminder to a more capable scheduler bot has been incredibly rewarding. It shows that with a few good libraries and a bit of Python, you can elevate your Telegram bots from simple responders to truly proactive assistants. Give it a shot, experiment, and let me know what cool scheduling ideas you come up with!

Happy bot building!

🕒 Last updated:  ·  Originally published: March 14, 2026

💬
Written by Jake Chen

Bot developer who has built 50+ chatbots across Discord, Telegram, Slack, and WhatsApp. Specializes in conversational AI and NLP.

Learn more →
Browse Topics: Best Practices | Bot Building | Bot Development | Business | Operations
Scroll to Top