Telegram Bot with AWS DynamoDB
Build a Telegram task bot that stores and retrieves data in DynamoDB.
Introduction
30 Second Summary
Every team has tasks flying around in group chats, sticky notes, and spreadsheets. What if someone could just type a command in a chat app and have that task instantly saved to a real cloud database for everyone to see?
In this project, you will build a Telegram bot called Allocakita that creates, lists, and completes tasks. All task data is stored in AWS DynamoDB, so it persists even when the bot restarts.
What You'll Build
You will type a slash command in Telegram, watch a confirmation pop up with an auto-generated Task ID, and then verify the item sitting in a real cloud database table.
By the end of this project, you'll have:
- A working chat command (/newtask) that parses task details from a message and writes them to a cloud database with a unique Task ID.
- A task dashboard in chat where you send /tasks to see every task's status, assignee, and priority, and /done to mark tasks as completed.
- A persistent cloud backend in DynamoDB that keeps tasks alive across restarts, so anyone running the bot sees the same data.
- Secret Mission: Add a /mytasks command that filters tasks by the Telegram user who created them.
Are there any prerequisites?
You will need an AWS account (free tier is fine) and a Telegram account. Basic Python knowledge helps, but the project walks you through every line of code.
Before We Start
To build Allocakita, your team's Telegram task bot, you need a few tools in place first.
In this step you will install Python, the AWS CLI, and the libraries that let your bot talk to a cloud database. You will also create the bot itself inside Telegram so you have a token to connect with.
In this step, get ready to:
- Install Python 3.12 and AWS CLI v2.
- Create a Telegram bot and grab its token.
- Set up the project directory with a virtual environment and dependencies.
Install Python 3.12
Python is the language your bot and all its AWS integrations are written in. This project requires version 3.12 or higher.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check if Python 3.12 is installed by running this command:
python3 --version
✔️ I see version 3.12 or higher
You are ready to go. Move on to installing the AWS CLI below.
ⓧ I see an older version
macOS often ships with an older Python that shadows newer installs. You need to install Python 3.12 separately.
- Visit python.org/downloads and download the Python 3.12 installer for your operating system.
- Run the installer. On Windows, check Add python.exe to PATH before clicking Install Now.
- Close and reopen your terminal, then run python3 --version again to confirm it now shows 3.12 or higher.
macOS still shows the old version?
Try running python3.12 --version instead. If that works, use python3.12 in place of python3 for the rest of this project.
ⓧ Command not found
- Visit python.org/downloads and download the Python 3.12 installer for your operating system.
- Run the installer. On Windows, check Add python.exe to PATH before clicking Install Now.
- Close and reopen your terminal, then run python3 --version again to confirm it shows 3.12 or higher.
Install the AWS CLI v2
The AWS CLI lets you interact with AWS services from your terminal. You will use it to configure credentials and later to manage your DynamoDB table.
- Check if the AWS CLI is already installed by running:
aws --version
✔️ I see version 2.x.x
Your AWS CLI is ready. Skip ahead to configuring your credentials below.
ⓧ I see version 1.x.x
You have an older major version. AWS CLI v2 is a separate install that replaces v1.
macOS
- Install AWS CLI v2 by running this command:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" && sudo installer -pkg AWSCLIV2.pkg -target /
- Enter your Mac password when prompted (you will not see characters as you type).
- Run aws --version again to confirm it now shows aws-cli/2.x.x.
Windows
- Download and run the official MSI installer from: https://awscli.amazonaws.com/AWSCLIV2.msi.
- Follow the installer prompts and accept the defaults.
- Close and reopen PowerShell, then run aws --version to confirm it shows aws-cli/2.x.x.
ⓧ Command not found
macOS
- Install AWS CLI v2 by running this command:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" && sudo installer -pkg AWSCLIV2.pkg -target /
- Enter your Mac password when prompted (you will not see characters as you type).
- Run aws --version to confirm it shows aws-cli/2.x.x.
Windows
- Download and run the official MSI installer from: https://awscli.amazonaws.com/AWSCLIV2.msi.
- Follow the installer prompts and accept the defaults.
- Close and reopen PowerShell, then run aws --version to confirm it shows aws-cli/2.x.x.
Configure your AWS credentials
The CLI needs your AWS access keys so it can create resources in your account on your behalf.
- Run the configuration wizard:
aws configure
What will this command ask?
The command prompts you for four values, one at a time. Enter them as follows:
- AWS Access Key ID - paste your IAM user's access key.
- AWS Secret Access Key - paste your IAM user's secret key.
- Default region name - type us-east-1.
- Default output format - type json.
- Confirm the configuration worked by running:
aws sts get-caller-identity
You should see a JSON response containing your AWS account ID and user ARN. This proves the CLI can authenticate.
Seeing 'InvalidClientTokenId'?
- Double-check you pasted the full Access Key ID and Secret Access Key without any extra spaces.
- Make sure the IAM user has programmatic access enabled.
- Run aws configure again to re-enter your credentials.
Still stuck? Help me troubleshoot my AWS CLI credentials.
Create a Telegram bot via BotFather
Every Telegram bot is created through BotFather, a special Telegram account that registers new bots and gives you a token to control them.
- Open Telegram on your phone or desktop.
- In the search bar, type @BotFather and open the conversation with the verified account (it has a blue checkmark).
- Send the message /newbot.
- When BotFather asks for a name, type Allocakita and press send.
- When BotFather asks for a username, type a unique username ending in _bot (for example, allocakita_yourname_bot).
- Copy the bot token from BotFather's reply. It looks like a long string of numbers and letters separated by a colon.
What is this token?
The token is like a password that lets your Python code send and receive messages as this bot. Keep it private. Anyone with the token can control your bot.
You now have the three external prerequisites: Python, the AWS CLI, and a Telegram bot token.
Set up the project directory
With your tools ready, it is time to create the project folder, isolate your dependencies in a virtual environment, and install the two Python libraries you need.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir allocakita
cd allocakita
- Confirm the folder exists by running:
ls
You should see an empty listing since the folder is brand new.
- Create a Python virtual environment by running:
python3 -m venv venv
- Activate the virtual environment:
macOS
source venv/bin/activate
Windows
venv\Scripts\Activate.ps1
Your terminal prompt now shows (venv) at the beginning. This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated copy of Python where you install packages without affecting your system Python. It keeps this project's dependencies separate from everything else on your machine.
- Install the two project dependencies by running:
pip install "python-telegram-bot==22.8" boto3
You should see pip downloading and installing both packages along with their dependencies. The final line will say "Successfully installed" followed by the package names.
What are these packages?
- python-telegram-bot==22.8 is a library that handles all the communication with the Telegram Bot API. It gives you a clean Python interface for receiving messages and sending replies.
- boto3 is AWS's official Python SDK. It lets your code create tables, write items, and query DynamoDB directly from Python.
pip not found?
- Make sure you activated the virtual environment first. You should see (venv) in your prompt.
- On some systems, try pip3 instead of pip.
Still stuck? Help me fix my pip installation.
Create the .env file
Your bot token is a secret. Instead of pasting it directly into code, you will store it in a .env file and read it at runtime.
- Create the .env file inside your allocakita folder by running:
echo "TELEGRAM_BOT_TOKEN=your-bot-token-here" > .env
- Open .env in a text editor and replace your-bot-token-here with the actual token you copied from BotFather.
- Save the file.
- Confirm the file exists by running:
ls -a
You should see .env and venv listed in the output.
Open VS Code in the project
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to Desktop, select the allocakita folder, and click Open.
You should see the allocakita folder name in the VS Code sidebar with .env and venv listed.
Don't see .env in the sidebar?
- Files starting with a dot are hidden on some systems. In VS Code they should still appear in the Explorer sidebar. If not, make sure you opened the correct folder.
Need help? Help me find my .env file in VS Code.
Your foundation is in place. You have Python, the AWS CLI, a Telegram bot token, and a clean project directory. Next up, you will write your first bot code and see it respond in Telegram.
Build a Basic Echo Bot
Your environment is set up, your dependencies are installed, and your bot token is ready. Now it is time to prove the pipeline works end to end.
Before integrating a database, you need proof that the Telegram bot actually receives and responds to messages. If this does not work, nothing else will either. In this step, you will write the bot skeleton and confirm it talks back in a live chat.
In this step, get ready to:
- Create bot.py with the bot framework and DynamoDB connection scaffolding.
- Run the bot locally and confirm it responds to /start in Telegram.
Write the bot skeleton
The bot needs a few things to start: a connection to your Telegram token, a reference to the DynamoDB table you will create in the next step, and at least one command handler. You will write all of this in a single file called bot.py.
- Create a new file called bot.py in your allocakita folder and add the following imports and configuration:
import os
from datetime import datetime, timezone
import boto3
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
# Load bot token from environment variable
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "your-bot-token-here")
# Connect to DynamoDB and reference the tasks table
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
table = dynamodb.Table("allocakita_tasks")
# Counter for generating sequential task IDs
task_counter = 0
def generate_task_id():
global task_counter
task_counter += 1
return f"TASK-{task_counter:03d}"
What does this code do?
This section sets up the foundation your bot needs before handling any commands.
- TELEGRAM_BOT_TOKEN reads your token from an environment variable so it is not hard-coded in the file.
- dynamodb and table create a reference to DynamoDB. The table does not exist yet, but this reference is safe to create ahead of time.
- generate_task_id() produces sequential IDs like TASK-001, TASK-002, etc. You will use this when creating tasks in a later step.
You will not see a visible change from this chunk on its own. The next chunk adds the command handler and entry point that bring the bot to life.
- Below the generate_task_id function, add the /start command handler and the main function:
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
# Reply with a greeting and list of available commands
await update.message.reply_text(
"Hello! I am Allocakita, your task manager.\n\n"
"Commands:\n"
"/newtask title | department | assignee | priority\n"
"/tasks - List all tasks\n"
"/done TASK-001 - Mark a task as completed"
)
def main() -> None:
# Build the bot application with our token
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
# Register the /start command handler
application.add_handler(CommandHandler("start", start))
# Start polling Telegram for updates
application.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
What does this code do?
This is where the bot comes alive.
- The start function is an async handler that fires whenever a user sends /start to the bot. It replies with a greeting and a list of commands.
- Application.builder().token(...).build() constructs the bot application using the python-telegram-bot builder pattern.
- run_polling() starts an infinite loop that checks Telegram for new messages. It blocks the terminal until you stop it with Ctrl+C.
- Save bot.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved the file, then move on to run the bot.
ⓧ I'd like to double check the full code
Scroll up and confirm your bot.py contains both chunks in this order:
- Chunk 1: Imports (os, datetime, boto3, telegram), the TELEGRAM_BOT_TOKEN variable, dynamodb and table references, task_counter, and the generate_task_id() function.
- Chunk 2: The start async handler, the main() function building the Application and registering the handler, and the if __name__ == "__main__" block.
Seeing a red underline in VS Code?
If VS Code shows import errors for telegram or boto3, make sure your virtual environment is selected as the Python interpreter.
- Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) and type Python: Select Interpreter.
- Choose the interpreter that shows ./venv/bin/python or .\venv\Scripts\python.
Help me fix import errors in VS Code for my Telegram bot
Run and test the bot
The bot code is ready. Now you need to load your Telegram token into the terminal session and start the bot.
- In your terminal, make sure you are in the allocakita folder with your virtual environment active.
- Export your bot token so the script can read it. Replace your-bot-token-here with the actual token BotFather gave you:
export TELEGRAM_BOT_TOKEN=your-bot-token-here
Why export instead of sourcing .env?
Python reads environment variables with os.environ.get(). For that to work, the variable must be exported to child processes. A plain source .env only sets a shell variable, which Python cannot see.
Before you run the bot, what do you think will happen when it starts? Will it print output, open a window, or do something else?
- Start the bot by running:
python3 bot.py
The terminal goes quiet. That is expected. run_polling() is silently checking Telegram for new messages in a loop. It blocks the terminal until you press Ctrl+C.
- Open Telegram on your phone or desktop.
- Search for the bot username you chose during BotFather setup (e.g. allocakita_bot).
- Tap Start or type /start and send it.
The bot replies with "Hello! I am Allocakita, your task manager." followed by a list of commands.
Bot not responding?
- Check your terminal for error messages. A 401 Unauthorized error means your token is wrong. Re-copy it from BotFather and export it again.
- Make sure you exported the token in the same terminal session where you ran python3 bot.py. Opening a new terminal tab loses the export.
- If you see ModuleNotFoundError, your virtual environment may not be active. Run source venv/bin/activate and try again.
Help me troubleshoot why my Telegram bot is not responding to /start
- Once you have confirmed the bot replies, go back to your terminal and press Ctrl+C to stop the bot.
Your bot is alive and responding. Next up, you will create the DynamoDB table that gives your tasks a permanent home in the cloud.
Create the DynamoDB Tasks Table
Your bot is alive and responding to commands in Telegram. But right now, anything it learns disappears the moment you stop the script.
Without a database, tasks exist only in memory. DynamoDB gives your bot a persistent, scalable store that costs nothing within the AWS Always Free Tier.
In this step, get ready to:
- Write a table creation script using boto3.
- Create the DynamoDB table with a task_id partition key.
- Verify the table is active and ready to accept items.
Write the table creation script
Instead of creating the table manually in the AWS Console, you will write a Python script that does it for you. This makes the setup repeatable and documents exactly how your table is configured.
- In VS Code, create a new file called create_table.py in your allocakita folder.
- Paste the following code into the file:
import boto3
def create_tasks_table():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
table = dynamodb.create_table(
TableName="allocakita_tasks",
KeySchema=[
{"AttributeName": "task_id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "task_id", "AttributeType": "S"},
],
ProvisionedThroughput={
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5,
},
)
print(f"Creating table '{table.table_name}'...")
table.wait_until_exists()
print(f"Table '{table.table_name}' created successfully. Status: ACTIVE")
if __name__ == "__main__":
create_tasks_table()
What does this code do?
- boto3.resource("dynamodb") connects to DynamoDB using the AWS credentials you configured earlier.
- KeySchema defines task_id as the partition key (HASH). Every item in the table needs a unique task_id.
- ProvisionedThroughput sets 5 Read Capacity Units and 5 Write Capacity Units. The DynamoDB free tier includes 25 of each, so this costs nothing.
- table.wait_until_exists() pauses the script until AWS finishes creating the table, then prints a confirmation.
What is a partition key?
A partition key is the primary way DynamoDB identifies each item in your table. Every item must have a unique partition key value.
In your case, task_id is the partition key. Each task you create will get a unique ID like TASK-001, which DynamoDB uses to store and retrieve it.
- Save create_table.py.
✔️ Awesome, I've got everything!
Great. Make sure you have saved the file before moving on.
ⓧ I'd like to double check the full code
Here is the complete create_table.py for comparison:
import boto3
def create_tasks_table():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
table = dynamodb.create_table(
TableName="allocakita_tasks",
KeySchema=[
{"AttributeName": "task_id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "task_id", "AttributeType": "S"},
],
ProvisionedThroughput={
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5,
},
)
print(f"Creating table '{table.table_name}'...")
table.wait_until_exists()
print(f"Table '{table.table_name}' created successfully. Status: ACTIVE")
if __name__ == "__main__":
create_tasks_table()
Run the script and verify in AWS
The script is ready. Before you run it, think about what you expect to happen.
What do you think you will see in the terminal when the script finishes? Will it be instant, or will it take a moment?
- Run the table creation script by entering this command in your terminal:
python3 create_table.py
The script pauses for a few seconds while AWS provisions the table. You should see this output:
Creating table 'allocakita_tasks'...
Table 'allocakita_tasks' created successfully. Status: ACTIVE
Seeing an error instead?
- If you see a credentials error, make sure you ran aws configure in Step 1 and entered your Access Key ID and Secret Access Key correctly.
- If you see a "table already exists" error, the table was already created. You can skip ahead to the verification step below.
- If you see a "region" error, confirm your region is set to us-east-1 by running aws configure get region.
Help me debug my create_table.py script
You can also verify the table exists in the AWS Console if you want a visual confirmation.
- Open the AWS Console in your browser.
- Make sure the region in the top-right corner is set to US East (N. Virginia) us-east-1.
- Click Tables in the left sidebar.
- You should see allocakita_tasks listed with status Active.
Will this cost me anything?
No. The DynamoDB Always Free Tier includes 25 GB of storage, 25 Read Capacity Units, and 25 Write Capacity Units per month. Your table uses only 5 of each, so you are well within the free allowance.
Your database is ready and waiting for tasks. Next up, you will wire the bot's /newtask command directly to this table so that a message in Telegram creates a real item in the cloud.
Wire the /newtask Command to DynamoDB
Your bot responds to /start and your DynamoDB table exists. Right now these two pieces are completely disconnected.
This step is where the real value lives. A user types a command in Telegram, and a task appears in your cloud database. By the end, anyone on your team can create a task from their phone and it persists in AWS.
In this step, get ready to:
- Build a handler that parses the /newtask command format.
- Write task data to DynamoDB with error handling for bad input.
- Test the full flow from Telegram to the cloud database.
Build the task creation handler
The /newtask command expects the user to send task details separated by pipe characters. The handler needs to validate the input, generate a unique ID, and write the item to DynamoDB.
- In VS Code, open bot.py and find the start function. Add the following new_task function directly below it:
async def new_task(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
# Check if the user provided any arguments
if not context.args:
await update.message.reply_text(
"Usage: /newtask title | department | assignee | priority\n"
"Example: /newtask Buy office supplies | Admin | @john | high"
)
return
# Join all arguments and split by the pipe character
raw_text = " ".join(context.args)
parts = [p.strip() for p in raw_text.split("|")]
# Validate the format has exactly 4 parts
if len(parts) != 4:
await update.message.reply_text(
"Invalid format. Use: /newtask title | department | assignee | priority"
)
return
What does this code do?
This is the first half of the new_task handler. It defines two validation gates before any database write happens.
- context.args is a list of words the user typed after the command, split by whitespace. If the list is empty, the user sent /newtask with nothing else.
- The code joins the args back into one string and splits on | instead. This lets the user include spaces in a task title like "Buy office supplies".
- If the split does not produce exactly 4 parts, the format is wrong and the bot replies with the correct usage.
- Directly below the code you just added (still inside the new_task function), add the task creation logic:
# Unpack the four parsed values
title, department, assignee, priority = parts
task_id = generate_task_id()
# Get the Telegram user who sent the command
user = update.effective_user
created_by = user.username if user.username else str(user.id)
now = datetime.now(timezone.utc).isoformat()
# Write the task item to DynamoDB
table.put_item(
Item={
"task_id": task_id,
"title": title,
"department": department,
"assignee": assignee,
"priority": priority,
"status": "open",
"created_by": created_by,
"created_date": now,
"last_updated": now,
"notes": "",
}
)
# Send confirmation back to the user
await update.message.reply_text(f"Task {task_id} created!\n\n{title} | {department} | {assignee} | {priority}")
What does this code do?
This is the happy path. Once validation passes, the handler builds and stores the task.
- generate_task_id() increments the global counter and returns a formatted ID like TASK-001. This function already exists in your file from the previous step.
- update.effective_user grabs the Telegram user object. If they have a username it uses that. Otherwise it falls back to their numeric user ID.
- table.put_item() writes a new item to the allocakita_tasks table with all the fields your schema needs.
- The bot replies with the generated task ID and a summary so the user has immediate confirmation.
Register the handler and test the full flow
The function exists but your application does not know about it yet. You need to register it as a command handler so Telegram routes /newtask messages to it.
- In bot.py, scroll down to the main() function and find this line:
application.add_handler(CommandHandler("start", start))
- Add this line directly below it:
application.add_handler(CommandHandler("newtask", new_task))
- Save bot.py.
What does this line do?
This tells the application to call your new_task function whenever a user sends /newtask in the chat. Without this registration, Telegram would deliver the message but the bot would ignore it.
Before you run the bot, think about what you expect to happen. If you send /newtask Buy office supplies | Admin | @john | high, do you think the bot will respond with a task ID or an error?
- If your bot is still running from the previous step, stop it with Ctrl+C in your terminal.
- Start the bot again by running:
python3 bot.py
- Switch to Telegram and open your Allocakita bot chat.
- Send this message:
/newtask Buy office supplies | Admin | @john | high
The bot replies with "Task TASK-001 created!" followed by a summary of the task details.
- Now test the error handling. Send /newtask with no arguments.
The bot replies with the usage instructions showing the expected format.
- Send a message with the wrong number of pipe-separated values:
/newtask Buy office supplies | Admin
The bot replies with "Invalid format" and shows the correct pattern.
Bot not responding to /newtask?
- Make sure you saved bot.py and restarted the bot after adding the new handler.
- Check that the add_handler line for newtask is inside the main() function, after the start handler registration.
- Look at your terminal for error messages. A common issue is an indentation error in the new_task function.
Help me debug why my Telegram bot is not responding to the /newtask command.
- Optionally, verify the data landed in AWS. Open the AWS Console in your browser.
- Navigate to DynamoDB and select Tables.
- Click allocakita_tasks, then select Explore table items.
- You should see your task item with task_id set to TASK-001, status set to open, and all the fields you defined.
✔️ Awesome, I've got everything!
Great. Double check you have saved bot.py and your bot is still running.
ⓧ I'd like to double check the full code
Your bot.py is now longer than 30 lines, so here is a checklist of what should be present, in order from top to bottom:
- Imports: os, datetime, boto3, and the telegram imports.
- Constants: TELEGRAM_BOT_TOKEN, dynamodb resource, table reference, and task_counter.
- The generate_task_id() function.
- The start() async handler.
- The new_task() async handler with error checking, parsing, DynamoDB write, and confirmation reply.
- The main() function with both start and newtask handlers registered.
- The if __name__ == "__main__" block at the bottom.
Your bot now writes real data to the cloud. In the next step, you will add commands to retrieve and complete tasks, turning this into a full task tracker your team can actually use.
Add the /tasks and /done Commands
Your bot can create tasks and store them in DynamoDB. That is only half the value. Without a way to view and complete tasks, the bot is a write-only tool nobody will actually use.
In this step, you will add two new commands that make Allocakita a real task tracker. /tasks retrieves every item from the database and displays them. /done updates a task's status to completed.
In this step, get ready to:
- Build a /tasks command that scans DynamoDB and formats results for Telegram.
- Build a /done command that updates a task's status using a conditional write.
- Test the complete create-list-complete-list flow end to end.
Build the /tasks list command
The /tasks command needs to read every task from DynamoDB and display them in a readable format. DynamoDB's scan() operation retrieves all items from a table.
- Stop your running bot by pressing Ctrl+C in the terminal.
- In bot.py, find the end of the new_task function (the last reply_text line inside it).
- Add the following function below it:
async def list_tasks(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
# Scan the entire table to get all tasks
response = table.scan()
items = response.get("Items", [])
if not items:
await update.message.reply_text("No tasks found. Create one with /newtask")
return
# Format each task as a readable line
lines = []
for item in items:
line = (
f"[{item['task_id']}] {item['title']} | "
f"{item['department']} | {item['assignee']} | "
f"{item['priority']} | {item['status']}"
)
lines.append(line)
await update.message.reply_text("\n".join(lines))
What does this code do?
table.scan() retrieves every item from your DynamoDB table. The response stores items under the "Items" key as a list of dictionaries.
If no tasks exist, the bot replies with a helpful hint instead of an empty message. Otherwise it loops through each item and formats it as a single line showing the task ID, title, department, assignee, priority, and status.
- Now register this handler. Find the main() function at the bottom of bot.py.
- Find this line:
application.add_handler(CommandHandler("newtask", new_task))
- Add this line directly below it:
application.add_handler(CommandHandler("tasks", list_tasks))
- Save bot.py.
- Restart the bot by running:
python3 bot.py
- Switch to Telegram and send /tasks to your bot.
You should see a formatted list of the tasks you created in the previous step, showing each task's ID, title, department, assignee, priority, and status.
Seeing "No tasks found" when you have tasks?
If you stopped and restarted the bot between steps, the in-memory task counter resets. Tasks created in previous sessions are still in DynamoDB. Try creating a fresh task with /newtask and then run /tasks again.
Also check that your AWS credentials are still active. Run aws sts get-caller-identity in a separate terminal to confirm.
Help me debug why /tasks returns no items
Build the /done command
Viewing tasks is useful, but marking them as completed is what makes this a real workflow tool. The /done command uses DynamoDB's update_item() to change a task's status field.
- Stop your bot again with Ctrl+C.
- In bot.py, find the end of the list_tasks function you just added.
- Add the following function below it:
async def done_task(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not context.args:
await update.message.reply_text("Usage: /done TASK-001")
return
# Normalize the task ID to uppercase
task_id = context.args[0].upper()
now = datetime.now(timezone.utc).isoformat()
try:
# Update status to completed, only if the task exists
table.update_item(
Key={"task_id": task_id},
UpdateExpression="SET #s = :status, last_updated = :updated",
ExpressionAttributeNames={"#s": "status"},
ExpressionAttributeValues={
":status": "completed",
":updated": now,
},
ConditionExpression="attribute_exists(task_id)",
)
await update.message.reply_text(f"Task {task_id} marked as completed!")
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException:
await update.message.reply_text(f"Task {task_id} not found.")
What does this code do?
context.args[0].upper() grabs the first word after /done and converts it to uppercase so that task-001 matches TASK-001 in the database.
The UpdateExpression uses #s as a placeholder for the status field because "status" is a reserved word in DynamoDB. Without the alias, DynamoDB rejects the query.
ConditionExpression="attribute_exists(task_id)" ensures the update only runs if a task with that ID actually exists. If it does not, DynamoDB throws a ConditionalCheckFailedException which the bot catches and returns a friendly error.
- Now register this handler. In the main() function, find the line you just added:
application.add_handler(CommandHandler("tasks", list_tasks))
- Add this line directly below it:
application.add_handler(CommandHandler("done", done_task))
- Save bot.py.
- Restart the bot by running:
python3 bot.py
- In Telegram, send /done TASK-001 to your bot.
You should see the reply "Task TASK-001 marked as completed!" confirming the status update.
Getting "Task not found" for a task you created?
The task ID is case-sensitive in DynamoDB. Make sure you use the exact ID that was returned when you created the task (e.g. TASK-001). If you restarted the bot since creating tasks, the counter resets. Create a new task with /newtask and use the new ID.
Help me debug why /done says task not found
Test the full create-list-complete-list flow
Now that both commands are working individually, test them together in the sequence your team will use every day.
- In Telegram, send this message to create a fresh task:
/newtask Prepare weekly report | Marketing | @alice | high
- Confirm the bot replies with a task ID and the task details.
- Send /tasks to see all tasks listed with their status as open.
Before you mark the task as done, what do you expect the status to show when you run /tasks again after completing it?
- Send /done followed by the task ID the bot just gave you (e.g. /done TASK-001).
- Send /tasks one more time.
The task you just completed now shows completed in the status column instead of open. That change persists in DynamoDB, so anyone running the bot sees the updated status.
Why does the data persist across restarts?
DynamoDB stores your data independently of the bot process. Even if you stop and restart bot.py, the tasks remain in the cloud. The only value that resets is the in-memory task_counter, which means new task IDs may overlap with existing ones.
✔️ Awesome, I've got everything!
Great. Double check you have saved bot.py and that your bot is still running.
ⓧ I'd like to double check the full code
Scroll through your bot.py and confirm the following are present, in this order:
- Imports (os, datetime, boto3, telegram modules).
- Token, DynamoDB resource, table reference, and task_counter globals.
- generate_task_id() function.
- start() handler.
- new_task() handler.
- list_tasks() handler (the one you added in this step).
- done_task() handler (the one you added in this step).
- main() with four add_handler calls: start, newtask, tasks, done.
Your bot is now a complete task tracker. Users can create tasks, view all tasks, and mark them as done. Next up, you will tackle a Secret Mission that adds user-specific filtering.
Secret mission
Add /mytasks Filtering
In a multi-user team, everyone creates tasks. But when you run /tasks, you see everything from everyone. Can you add a /mytasks command that filters tasks by the Telegram user who created them?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. The DynamoDB table is within the AWS Always Free Tier and costs $0 as long as you stay within limits. The bot runs locally, so stopping it has no ongoing cost.
Resources you used:
- DynamoDB table allocakita_tasks in your AWS account (us-east-1).
- Local project folder allocakita on your Desktop (includes bot.py, create_table.py, .env, and the venv virtual environment).
- Telegram bot created via @BotFather.
Keep everything running
No action needed. Choose this if you are still actively building on the Allocakita bot or plan to extend it with your Cloud, Data, or AI team next week.
- The DynamoDB table persists at no cost within the Always Free Tier (25 GB storage, 25 RCU, 25 WCU).
- Stop the bot process with Ctrl+C in your terminal. You can restart it anytime with python3 bot.py.
- Your Telegram bot stays registered and ready to use whenever you run the script again.
Pause - I'll come back to this later
Shut down the bot process to free up your terminal. Your table and bot registration stay intact with no cost.
- Press Ctrl+C in the terminal running bot.py to stop the bot.
- The DynamoDB table stays in your account at zero cost when idle (within free tier limits).
- When you are ready to resume, navigate to your allocakita folder, activate the virtual environment with source venv/bin/activate, and run python3 bot.py.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is permanent.
- Stop the bot process with Ctrl+C if it is still running.
- Delete the DynamoDB table by running this command in your terminal:
aws dynamodb delete-table --table-name allocakita_tasks
- Delete the local project folder. On macOS/Linux:
rm -rf ~/Desktop/allocakita
On Windows:
rmdir /s /q %USERPROFILE%\Desktop\allocakita
- Delete the Telegram bot by opening a chat with @BotFather in Telegram.
- Send /deletebot, select your bot from the list, and confirm the deletion when prompted.
Nice Work!
Nice Work!
You just built a cloud-connected task management bot from scratch. Allocakita can create, list, and complete tasks through Telegram commands, with every piece of data persisting in a real AWS database.
You've learned how to:
- Build a Telegram bot using python-telegram-bot 22.8 with async command handlers that parse user input and respond with structured feedback.
- Design and create a DynamoDB table with a partition key schema and wire it to your bot using boto3 CRUD operations (create, read, update).
- Handle error cases gracefully, including missing arguments, malformed input, and non-existent task IDs, so the bot always gives useful feedback.
- (Secret Mission) Added user-specific task filtering using DynamoDB FilterExpression to let each team member see only their own tasks.
Ready to quiz yourself?