Serverless architecture offers a great way to build and deploy applications without managing servers. In this post, we’ll walk through the process of creating a Telegram bot using AWS Lambda for serverless execution, Terraform for infrastructure as code, and Python for the bot’s logic. We’ll also use a Lambda Layer to manage our dependencies efficiently.
Project Structure
Let’s start with our project structure:
Initial Project Structure
telegram-bot/
├── terraform/
│ ├── main.tf
│ └── variables.tf
├── src/
│ └── bot.py
├── requirements.txt
├── build_layer.sh
└── README.md
Project Structure After Building
After running our build scripts, the structure will look like this:
telegram-bot/
├── terraform/
│ ├── main.tf
│ └── variables.tf
├── src/
│ └── bot.py
├── requirements.txt
├── build_layer.sh
├── layer.zip
├── bot.zip
└── README.md
Implementing the Bot Logic
First, let’s implement the bot logic in Python. Create a bot.py
file in the src
folder:
import asyncio
import json
import os
import traceback
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes, filters
# Initialize the bot application
bot_app = ApplicationBuilder().token(os.getenv('TELEGRAM_BOT_TOKEN')).build()
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Hello! I'm your friendly neighborhood AWS Lambda bot. 🤖")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
text = update.message.text
response = f"You said: {text}\nI'm just a simple echo bot for now, but I have big dreams! 🌟"
await update.message.reply_text(response)
# Add handlers
bot_app.add_handler(CommandHandler("start", start))
bot_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
async def tg_bot_main(bot_app, event):
async with bot_app:
await bot_app.process_update(
Update.de_json(json.loads(event["body"]), bot_app.bot)
)
def lambda_handler(event, context):
try:
asyncio.run(tg_bot_main(bot_app, event))
except Exception as e:
traceback.print_exc()
print(e)
return {"statusCode": 500, "body": json.dumps("Oops! Something went wrong 😅")}
return {"statusCode": 200, "body": json.dumps("Message processed successfully")}
Creating a Lambda Layer for Dependencies
To manage our dependencies efficiently, we’ll use a Lambda Layer. First, create a requirements.txt
file in your project root:
python-telegram-bot==13.7
Next, create a script named build_layer.sh
in your project root to build the layer:
#!/bin/bash
# Create a directory for the layer
mkdir -p layer/python
# Install dependencies into the layer directory
pip install -r requirements.txt -t layer/python
# Create a zip file of the layer
cd layer
zip -r ../layer.zip .
cd ..
# Clean up
rm -rf layer
echo "Layer package created: layer.zip"
Make the script executable and run it:
chmod +x build_layer.sh
./build_layer.sh
Setting Up Infrastructure with Terraform
Now, let’s set up our AWS infrastructure using Terraform. Create a main.tf
file in the terraform
folder:
provider "aws" {
region = "us-east-1"
}
resource "aws_lambda_layer_version" "dependencies_layer" {
filename = "layer.zip"
layer_name = "telegram-bot-dependencies"
compatible_runtimes = ["python3.12"]
}
resource "aws_lambda_function" "telegram_bot" {
filename = "src/bot.zip"
function_name = "telegram-bot"
role = aws_iam_role.lambda_exec.arn
handler = "bot.lambda_handler"
runtime = "python3.12"
layers = [aws_lambda_layer_version.dependencies_layer.arn]
environment {
variables = {
TELEGRAM_BOT_TOKEN = var.telegram_bot_token
}
}
}
resource "aws_lambda_function_url" "telegram_bot_url" {
function_name = aws_lambda_function.telegram_bot.function_name
authorization_type = "NONE"
cors {
allow_credentials = true
allow_origins = ["*"]
allow_methods = ["POST"]
allow_headers = ["*"]
expose_headers = ["keep-alive", "date"]
}
}
resource "aws_iam_role" "lambda_exec" {
name = "telegram_bot_lambda"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.lambda_exec.name
}
output "function_url" {
description = "Function URL for the Lambda"
value = aws_lambda_function_url.telegram_bot_url.function_url
}
variable "telegram_bot_token" {
type = string
description = "Telegram Bot API Token"
}
Create a variables.tf
file to define the Telegram bot token variable:
variable "telegram_bot_token" {
type = string
description = "Telegram Bot API Token"
}
Deploying the Bot
To deploy the bot, follow these steps:
-
Set up the Telegram bot:
- Open Telegram and search for the BotFather
- Start a chat and use the
/newbot
command to create a new bot - Save the API token provided by BotFather
-
Build the Lambda Layer:
./build_layer.sh
-
Prepare the Lambda function:
cd src zip bot.zip bot.py cd ..
-
Initialize Terraform:
cd terraform terraform init
-
Deploy the infrastructure:
terraform apply -var="telegram_bot_token=YOUR_BOT_TOKEN"
Replace
YOUR_BOT_TOKEN
with the token you got from BotFather. -
After the deployment is complete, use the output URL to set the webhook for your Telegram bot:
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook?url=<YOUR_API_GATEWAY_URL>
Testing the Bot
To test your bot, simply start a chat with it on Telegram and send a message. The bot should respond according to the logic implemented in bot.py
.
Updating the Bot
When you need to update your bot’s code or dependencies:
-
For code changes, update
src/bot.py
and rezip it:cd src zip -u bot.zip bot.py cd ..
-
For dependency changes, update
requirements.txt
and rebuild the layer:./build_layer.sh
-
Apply the changes with Terraform:
cd terraform terraform apply -var="telegram_bot_token=YOUR_BOT_TOKEN"
Conclusion
We’ve successfully created a serverless Telegram bot using AWS Lambda, Lambda Layers, and Terraform. This approach allows for easy scaling, maintenance, and dependency management of your bot. You can extend the bot’s functionality by modifying the Python code in the Lambda function and updating the deployment as needed.
Remember to clean up your AWS resources if you no longer need them to avoid unnecessary charges:
terraform destroy -var="telegram_bot_token=YOUR_BOT_TOKEN"