Deploying Telegram Bot to AWS Lambda with Function URL

December 20, 2024 Go Telegram Aws Lambda Deploy Function-Url Cli


This article is a continuation of “Building an AI Telegram Bot with Go, Gemini API, and AWS Lambda” and contains detailed instructions for setting up and deploying a Telegram bot to AWS Lambda using Function URL.

Introduction

In the previous article, we explored the architecture of a Telegram bot with an AI router and Gemini API integration. Now we’ll dive deep into the deployment process of this bot to AWS Lambda, including AWS CLI setup, Lambda function creation, Function URL configuration, and deployment automation.

Setup: AWS CLI and Authentication

Before working with AWS Lambda, you need to install and configure AWS CLI. AWS CLI is a unified command-line tool for managing AWS services.

Installing AWS CLI

# macOS
brew install awscli

# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Verify installation
aws --version

Configuring Authentication

AWS CLI uses several authentication methods. The simplest and most secure way to get started is using AWS IAM Identity Center (formerly AWS SSO) or direct IAM credentials.

Method 1: AWS IAM Identity Center (recommended for organizations)

# Configure SSO profile
aws configure sso

# You'll be prompted for:
# - SSO start URL: https://your-company.awsapps.com/start
# - SSO region: us-east-1 (or your region)
# - SSO account ID: 123456789012
# - SSO role name: AdministratorAccess (or another role)
# - CLI default region: us-east-1
# - CLI default output format: json

# Login to SSO
aws sso login --profile your-profile-name

Method 2: Direct IAM Credentials (for personal projects)

# Configure credentials
aws configure

# You'll be prompted to enter:
# AWS Access Key ID: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region name: us-east-1
# Default output format: json

Verify Connection

# Check current user/role
aws sts get-caller-identity

# Should return something like:
# {
#     "UserId": "AIDAIOSFODNN7EXAMPLE",
#     "Account": "123456789012",
#     "Arn": "arn:aws:iam::123456789012:user/your-username"
# }

Working with Multiple Profiles

If you have multiple AWS accounts, you can use profiles:

# Create a profile
aws configure --profile production

# Use a profile
aws lambda list-functions --profile production

# Or set environment variable
export AWS_PROFILE=production

Request Processing Architecture

Before diving into creating the Lambda function, let’s understand how AWS processes requests from Telegram:

  sequenceDiagram
    participant User as Telegram User
    participant TG as Telegram API
    participant FunctionURL as Lambda Function URL
    participant Lambda as Lambda Function
    participant S3 as AWS S3
    participant Gemini as Google Gemini API
    participant CloudWatch as CloudWatch Logs

    User->>TG: Sends message to bot
    TG->>FunctionURL: POST webhook (JSON update)
    FunctionURL->>Lambda: Invokes function
    Lambda->>CloudWatch: Logs execution start
    
    Lambda->>S3: Loads conversation history (chatID)
    S3-->>Lambda: Returns history (JSON)
    
    Lambda->>Lambda: Router determines request type
    alt Text Request
        Lambda->>Gemini: Chat API request with context
        Gemini-->>Lambda: Text response
    else Image Generation
        Lambda->>Gemini: Image Generation API
        Gemini-->>Lambda: Image
    end
    
    Lambda->>TG: Sends response to user
    Lambda->>S3: Saves updated history
    Lambda->>CloudWatch: Logs completion
    
    Lambda-->>FunctionURL: HTTP 200 OK
    FunctionURL-->>TG: Acknowledgment
    TG-->>User: Shows bot response

Preparing Code for Lambda

To work with Lambda Function URL, we need to adapt our code. Function URL uses standard HTTP format, not API Gateway format. Here’s the adapted handler:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
    "google.golang.org/api/option"
    "google.golang.org/api/generativeai"
)

var (
    bot    *tgbotapi.BotAPI
    gemini *generativeai.Client
    s3Client *s3.Client
)

func init() {
    // Initialize on Lambda cold start
    cfg := loadConfig()
    
    var err error
    bot, err = tgbotapi.NewBotAPI(cfg.TelegramToken)
    if err != nil {
        panic(fmt.Sprintf("Failed to create bot: %v", err))
    }
    
    gemini, err = generativeai.NewClient(context.Background(),
        option.WithAPIKey(cfg.GeminiAPIKey))
    if err != nil {
        panic(fmt.Sprintf("Failed to create Gemini client: %v", err))
    }
    
    // Initialize S3 client
    awsCfg, err := config.LoadDefaultConfig(context.Background())
    if err != nil {
        panic(fmt.Sprintf("Failed to load AWS config: %v", err))
    }
    s3Client = s3.NewFromConfig(awsCfg)
}

func Handler(ctx context.Context, request events.LambdaFunctionURLRequest) 
    (events.LambdaFunctionURLResponse, error) {
    
    // Parse Telegram webhook update
    var update tgbotapi.Update
    if err := json.Unmarshal([]byte(request.Body), &update); err != nil {
        return events.LambdaFunctionURLResponse{
            StatusCode: http.StatusBadRequest,
            Body:       fmt.Sprintf("Invalid request: %v", err),
        }, nil
    }
    
    // Process update asynchronously
    go handleMessage(ctx, update)
    
    // Immediately return response to Telegram
    return events.LambdaFunctionURLResponse{
        StatusCode: http.StatusOK,
        Body:       "OK",
    }, nil
}

func main() {
    lambda.Start(Handler)
}

Creating Lambda Function via AWS CLI

Now let’s create the Lambda function using modern AWS CLI v2:

Step 1: Create IAM Role for Lambda

First, let’s create a role that the Lambda function will use:

# Create trust policy for Lambda
cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# Create role
aws iam create-role \
  --role-name telegram-bot-lambda-role \
  --assume-role-policy-document file://trust-policy.json

# Attach basic execution policy for Lambda
aws iam attach-role-policy \
  --role-name telegram-bot-lambda-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

# Create policy for S3 access
cat > s3-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::bot-conversations/*"
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name telegram-bot-lambda-role \
  --policy-name S3AccessPolicy \
  --policy-document file://s3-policy.json

# Get role ARN (needed for function creation)
ROLE_ARN=$(aws iam get-role \
  --role-name telegram-bot-lambda-role \
  --query 'Role.Arn' \
  --output text)

echo "Role ARN: $ROLE_ARN"

Step 2: Create S3 Bucket for Conversations (optional)

# Create bucket (bucket names must be globally unique)
BUCKET_NAME="telegram-bot-conversations-$(date +%s)"
aws s3 mb s3://$BUCKET_NAME --region us-east-1

# Save bucket name for later use
echo "BUCKET_NAME=$BUCKET_NAME" > .env.bucket

Step 3: Build Go Application

# Build for Linux (required for Lambda)
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap

# Create ZIP archive (Lambda requires bootstrap as executable name)
zip function.zip bootstrap

# Check size (Lambda has deployment package size limits)
ls -lh function.zip

Step 4: Create Lambda Function

# Create function (replace $ROLE_ARN with value from previous step)
aws lambda create-function \
  --function-name telegram-bot \
  --runtime provided.al2023 \
  --role $ROLE_ARN \
  --handler bootstrap \
  --zip-file fileb://function.zip \
  --timeout 30 \
  --memory-size 512 \
  --architectures arm64 \
  --environment Variables="{
    TELEGRAM_TOKEN=your-telegram-token,
    GEMINI_API_KEY=your-gemini-api-key,
    S3_BUCKET=$BUCKET_NAME
  }" \
  --description "Telegram bot with Gemini AI integration"

# Verify function creation
aws lambda get-function --function-name telegram-bot

Important Parameters:

  • --runtime provided.al2023: Uses Amazon Linux 2023 runtime for Go
  • --architectures arm64: Uses ARM64 architecture (cheaper and faster)
  • --timeout 30: Maximum execution time (seconds)
  • --memory-size 512: Allocated memory (MB). More memory = more CPU

Creating Lambda Function URL

Lambda Function URL is a simple way to provide an HTTP(S) endpoint for a Lambda function without needing to set up API Gateway. This is perfect for Telegram webhooks:

# Create Function URL with CORS (if needed)
aws lambda create-function-url-config \
  --function-name telegram-bot \
  --auth-type NONE \
  --cors '{
    "AllowOrigins": ["*"],
    "AllowMethods": ["POST"],
    "AllowHeaders": ["content-type"],
    "MaxAge": 300
  }'

# Get function URL
FUNCTION_URL=$(aws lambda get-function-url-config \
  --function-name telegram-bot \
  --query 'FunctionUrl' \
  --output text)

echo "Function URL: $FUNCTION_URL"

# Add permission for public access
aws lambda add-permission \
  --function-name telegram-bot \
  --statement-id FunctionURLAllowPublicAccess \
  --action lambda:InvokeFunctionUrl \
  --principal "*" \
  --function-url-auth-type NONE

Function URL Authentication Types:

  • NONE: Public access (suitable for Telegram webhook)
  • AWS_IAM: Requires AWS Signature Version 4 signing

Configure Telegram Webhook

Now let’s configure Telegram to send updates to our Function URL:

# Set webhook
TELEGRAM_TOKEN="your-telegram-bot-token"
curl -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/setWebhook" \
  -H "Content-Type: application/json" \
  -d "{\"url\": \"${FUNCTION_URL}\"}"

# Check webhook status
curl "https://api.telegram.org/bot${TELEGRAM_TOKEN}/getWebhookInfo"

Updating Function Code (Deployment)

When you need to update the function code:

# Rebuild
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bootstrap
zip function.zip bootstrap

# Update function code
aws lambda update-function-code \
  --function-name telegram-bot \
  --zip-file fileb://function.zip \
  --architectures arm64

# Update environment variables (if needed)
aws lambda update-function-configuration \
  --function-name telegram-bot \
  --environment Variables="{
    TELEGRAM_TOKEN=your-new-token,
    GEMINI_API_KEY=your-new-key,
    S3_BUCKET=$BUCKET_NAME
  }"

# Wait for update to complete
aws lambda wait function-updated --function-name telegram-bot

Monitoring and Logging

AWS Lambda automatically sends logs to CloudWatch:

# View recent logs
aws logs tail /aws/lambda/telegram-bot --follow

# View function metrics
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Invocations \
  --dimensions Name=FunctionName,Value=telegram-bot \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Sum

# View function information
aws lambda get-function \
  --function-name telegram-bot \
  --query 'Configuration.[FunctionName,Runtime,LastModified,MemorySize,Timeout]' \
  --output table

Deployment Automation (Makefile)

To simplify the deployment process, you can create a Makefile:

.PHONY: build deploy update logs clean

FUNCTION_NAME=telegram-bot
REGION=us-east-1

build:
	@echo "Building for Lambda..."
	GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bootstrap
	zip function.zip bootstrap
	@echo "Build complete. Size:"
	@ls -lh function.zip

deploy: build
	@echo "Deploying to Lambda..."
	aws lambda update-function-code \
		--function-name $(FUNCTION_NAME) \
		--zip-file fileb://function.zip \
		--region $(REGION)
	@echo "Waiting for update to complete..."
	aws lambda wait function-updated \
		--function-name $(FUNCTION_NAME) \
		--region $(REGION)
	@echo "Deployment complete!"

update-env:
	@echo "Updating environment variables..."
	aws lambda update-function-configuration \
		--function-name $(FUNCTION_NAME) \
		--environment Variables="{TELEGRAM_TOKEN=$(TELEGRAM_TOKEN),GEMINI_API_KEY=$(GEMINI_API_KEY),S3_BUCKET=$(S3_BUCKET)}" \
		--region $(REGION)

logs:
	@echo "Tailing logs..."
	aws logs tail /aws/lambda/$(FUNCTION_NAME) --follow --region $(REGION)

clean:
	rm -f bootstrap function.zip
	@echo "Clean complete"

Usage:

# Set environment variables
export TELEGRAM_TOKEN="your-token"
export GEMINI_API_KEY="your-key"
export S3_BUCKET="your-bucket-name"

# Build and deploy
make deploy

# View logs
make logs

Function URL vs API Gateway Benefits

Lambda Function URL:

  • ✅ Simpler to set up (single AWS CLI call)
  • ✅ Lower latency (no intermediate layer)
  • ✅ Cheaper (no API Gateway charges)
  • ✅ Perfect for simple webhooks

API Gateway:

  • ✅ More features (rate limiting, API keys, caching)
  • ✅ REST and WebSocket support
  • ✅ Integration with other AWS services
  • ✅ Better for public APIs

For a Telegram bot, Function URL is the optimal choice due to simplicity and cost-effectiveness.

Conclusion

In this article, we explored the detailed process of deploying a Telegram bot to AWS Lambda using Function URL. We set up AWS CLI, created IAM roles, deployed the Lambda function, and configured webhook integration with Telegram. Using Function URL simplifies the process compared to API Gateway and makes deployment more cost-effective for simple webhook scenarios.

For more information about creating the bot itself and its architecture, refer to the previous article: “Building an AI Telegram Bot with Go, Gemini API, and AWS Lambda”.

Tags:

Test Your Knowledge

1. What runtime is used for Go functions in AWS Lambda?
2. Which architecture is preferred for Lambda functions on Go?
3. What Function URL authentication type is used for Telegram webhook?
4. What executable name is required for Lambda function on Go?
5. What advantages does Lambda Function URL have over API Gateway?
6. How to verify AWS connection after CLI setup?
7. Where are Lambda function logs stored?
8. Which AWS CLI authentication method is recommended for organizations?

Related Articles

December 19, 2024

Building an AI Telegram Bot with Go, Gemini API, and AWS Lambda

In this article, we’ll explore how to build an intelligent Telegram bot using Go that acts as a proxy between users and Google’s Gemini API. The bot will handle two primary functions: answering user messages and generating images. While this mechanism can be significantly extended with additional capabilities like voice and video generation, we’ll focus on these two request types for simplicity.

Read More → Go Telegram Gemini Ai Aws Lambda Bot
July 1, 2020

Deploy docker swarm from Gitlab CI

Deploy docker swarm from Gitlab CI

Docker container deployment has many advantages over binary deployment. Let’s dive into advantages and see how we can implement docker container deployment from Gitlab CI. Let’s talk about how standard docker can help us. Any docker orchestration tools (Kubernetes, for example) will be observed in future articles.

Read More → Docker Deploy Gitlab Ci/Cd
May 8, 2020

Compilation and deploy via SSH in Gitlab CI

Compilation and deploy via SSH in Gitlab CI

Let’s take a look at how Go compilation works and how to use GitLab CI for that.

Read More → Gitlab Compilation Deploy Ssh Modules Vendor Ci/Cd