January 7, 2026
Photo by Fabian Irsara on Unsplash

If you’ve ever managed cloud services, you know how tricky it can get to keep everything running smoothly — especially when services need to be started or stopped outside normal business hours. At my company, our AWS ECS services are scheduled to run only during office hours to save costs.

But here’s the thing: developers and QA folks often need these services late at night or on weekends for urgent deliveries. And that means I get constant pings like:

“Hey, can you start the QA servers now? I need them running ASAP.”

Raat ke 1 baje koi time hai call krne ka

Honestly, it was frustrating. Changing the scheduler multiple times, responding to those messages, and manually controlling the services became a real pain. I knew there had to be a better way.

💡The Idea

What if I could let authorized people start or stop ECS services themselves — anytime, anywhere — just by sending a WhatsApp message? Everyone uses WhatsApp, it’s simple, and it’s fast.

Everyone uses WhatsApp, it’s simple, fast, and always available. So I built a lightweight WhatsApp bot that lets a few authorized users control our AWS ECS clusters using simple text commands.

No more waiting on me, no manual schedule overrides — just chat and control.

Ab tumhare hawale control sathiyo

🤔 How It Works?

Here’s what the user experience looks like:

  1. You send Hi to the bot’s WhatsApp number.
  2. The bot replies with a menu.
  3. You reply with a desired choice.
  4. The bot checks if your WhatsApp number is authorized.
  5. If you’re allowed, it talks to AWS ECS and updates the services accordingly.
  6. You get a confirmation message listing the services started/stopped.

And that’s it — no apps, no AWS Console login, just WhatsApp.

👀Behind the Scenes (Technical Overview)

  • Frontend: Twilio’s WhatsApp sandbox to receive/send messages.
  • Backend: An AWS Lambda function triggered via a public Lambda Function URL
  • Logic: The Lambda function uses boto3 to interact with ECS clusters and services.
  • Security: Only authorized numbers can issue commands. Logs are stored in CloudWatch for audit and debugging.

How to Set It Up — Step by Step 👇

Here’s the full setup so you can build this too:

1. Create a Twilio Account and Setup WhatsApp Sandbox

  • Go to Twilio and sign up for a free account if you don’t have one.
Twilio Signup Page
  • Navigate to Messaging > Try it out > WhatsApp Sandbox in the Twilio Console.
Twilio whatsapp sandbox
  • Follow the instructions to join the sandbox on your WhatsApp. Usually, you’ll need to scan the bar code and send a code message from your WhatsApp number to the sandbox number.
Connect to twilio sandbox
Twilio sandbox whatsapp
  • Make a note of your WhatsApp Sandbox number — this is the number you’ll use to message your bot during testing. Also, copy the Webhook URL field (we’ll come back and update it later once your Lambda function is ready).

2. Create an AWS Lambda Function

  • Go to AWS Console → Lambda → Create Function
  • Name it something like ControlECSService.
  • Runtime: Python 3.13
  • Architecture: x86_64
  • Permissions: Create a new role with basic Lambda permissions.
  • After creation, we’ll attach an inline policy for ECS access.

3. Set Up IAM Role and Permissions

Your Lambda function needs permissions to access ECS cluster and services:

  • Go to IAM service in AWS Console.
  • Create a new policy LambdaECSControlPolicy with the following ECS permissions:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecs:ListClusters",
                "ecs:ListServices"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecs:DescribeServices"
            ],
            "Resource": [
                "arn:aws:ecs:<AWS_REGION>:<AWS_ACCOUNT_ID>:cluster/development-cluster",
                "arn:aws:ecs:<AWS_REGION>:<AWS_ACCOUNT_ID>:cluster/qa-cluster"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecs:UpdateService"
            ],
            "Resource": [
                "arn:aws:ecs:<AWS_REGION>:<AWS_ACCOUNT_ID>:service/development-cluster/*",
                "arn:aws:ecs:<AWS_REGION>:<AWS_ACCOUNT_ID>:service/qa-cluster/*"
            ]
        }
    ]
}
  • Attach this policy to the IAM role used by your Lambda function.

4. Add the Lambda Code

  • Navigate to your Lambda function, replace the default code with the below code.
import json
import boto3
import base64
import logging
from datetime import datetime
from urllib.parse import parse_qs

# Initialize logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Map command environments to ECS cluster names
CLUSTER_MAP = {
    "DEV": "development-cluster",
    "QA": "qa-cluster"
}

# Map numbers to commands
OPTIONS_MAP = {
    "1": "DEV-START",
    "2": "DEV-STOP",
    "3": "QA-START",
    "4": "QA-STOP"
}

# Authorized WhatsApp numbers
AUTHORIZED_USERS = [
    "whatsapp:+91xxxxxxxxxx"  # Authorized numbers who joined twilio sandbox in step 1
]

ecs_client = boto3.client('ecs')

def lambda_handler(event, context):
    try:
        # Decode base64 body if needed
        if event.get("isBase64Encoded"):
            body_bytes = base64.b64decode(event["body"])
            decoded_body = body_bytes.decode("utf-8")
        else:
            decoded_body = event["body"]

        # Parse form-urlencoded body
        form_data = parse_qs(decoded_body)
        user_message = form_data.get("Body", [""])[0].strip().lower()
        from_number = form_data.get("From", [""])[0]

        timestamp = datetime.utcnow().isoformat()

        # Log incoming request
        logger.info(f"[{timestamp}] Message received from {from_number}: {user_message}")

        # Authorization check
        if from_number not in AUTHORIZED_USERS:
            logger.warning(f"[{timestamp}] Unauthorized access attempt from {from_number}")
            return twilio_response("⛔ You are not authorized to perform this action. Please contact the admin.")

        # Welcome message
        if user_message == "hi":
            return twilio_response(
                "👋 Hey there!\n\nI'm your friendly AWS helper bot.\nPlease choose an option by replying with a number:\n\n"
                "1️⃣ Start DEV services\n"
                "2️⃣ Stop DEV services\n"
                "3️⃣ Start QA services\n"
                "4️⃣ Stop QA services\n\n"
                "Just reply with 1, 2, 3, or 4 to proceed."
            )

        # Map user input to command
        command = OPTIONS_MAP.get(user_message)
        if not command:
            logger.info(f"[{timestamp}] Invalid input from {from_number}: {user_message}")
            return twilio_response(
                "⚠️ Oops! I didn't understand that.\n"
                "Please type *Hi* to see the list of valid options."
            )

        # Process command
        env, action = command.split("-")
        cluster_name = CLUSTER_MAP.get(env)
        desired_count = 1 if action == "START" else 0

        # List services in the cluster
        paginator = ecs_client.get_paginator('list_services')
        service_arns = []
        for page in paginator.paginate(cluster=cluster_name):
            service_arns.extend(page["serviceArns"])

        if not service_arns:
            logger.warning(f"[{timestamp}] No services found in {env} cluster for {from_number}")
            return twilio_response(f"⚠️ No services found in the *{env}* cluster to {action.lower()}.")

        # Start or stop services
        results = []
        for service_arn in service_arns:
            service_name = service_arn.split("/")[-1]
            ecs_client.update_service(
                cluster=cluster_name,
                service=service_name,
                desiredCount=desired_count
            )
            results.append(service_name)

        logger.info(f"[{timestamp}] {action} action executed in {env} by {from_number}: {results}")

        action_word = "✅ Started" if desired_count == 1 else "🛑 Stopped"
        message = (
            f"{action_word} the following services in *{env}* environment:\n" +
            "\n".join(f"• {name}" for name in results) +
            "\n\n👍 If you want to do something else, just type *Hi*."
        )
        if desired_count == 1:
            message += "\n⏳ Please wait a few minutes for changes to take effect."
        message += "\n📞 If you encounter any issues, please contact the administrator."

        return twilio_response(message)

    except Exception as e:
        error_time = datetime.utcnow().isoformat()
        logger.error(f"[{error_time}] Exception occurred for {from_number}: {str(e)}", exc_info=True)
        return twilio_response(f"❌ Something went wrong:\n{str(e)}\nPlease try again or type *Hi* for help.")

def twilio_response(message):
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/xml"
        },
        "body": f"<Response><Message>{message}</Message></Response>"
    }
  • Update the AUTHORIZED_USERS list with the WhatsApp numbers allowed to control your services (numbers must be in whatsapp:+<countrycode><number> format, e.g. whatsapp:+1234567890).

5. Enable Lambda Function URL (Public Endpoint)

  • Go to Lambda → Configuration → Function URL → Create function URL
  • Auth type: None (so Twilio can invoke it publicly).
Create lambda function
  • Save and copy the generated function URL — it’ll be your webhook.

6. Configure Twilio Webhook to Point to Lambda URL

  • Go back to Twilio Console → WhatsApp Sandbox settings.
  • Paste your Lambda URL in the field “When a message comes in”.
  • Set method: POST
Twilio whatsapp sandbox settings
  • Save the webhook URL.

7. Test Your Bot 🤖

  • Open WhatsApp on your authorized number.
  • Send a message Hi to your Twilio WhatsApp sandbox number.
  • You should receive a menu with options to start or stop DEV or QA ECS services.
  • Reply with 123, or 4 to test starting/stopping services.
  • Check your AWS ECS Console to see if services have updated accordingly.
Testing bot

8. Monitor Logs and Debug

  • Go to CloudWatch Logs in AWS Console.
  • Find the log group for your Lambda function.
  • Check logs for incoming requests, errors, and actions taken.
  • This helps in debugging and ensuring everything works smoothly.

😊 Why This Helps

  • No more late-night/weekend calls to start or stop servers.
  • Developers and QA get control on-demand, reducing delays.
  • No need to edit schedules manually again and again.
  • Keeps cloud resources optimized, running only when needed.

💰 Estimated Monthly Cost

Here’s a rough idea of what this setup might cost if you use it 50–100 times a month:

  • Twilio WhatsApp Sandbox: Free for development and testing.
  • AWS Lambda: Around $0.20 to $0.40 depending on usage.
  • Lambda Function URL: Free.
  • CloudWatch Logs: About $0.50 to $1.00 depending on how much logging you do.
  • ECS API calls: Usually a few cents, roughly $0.10 to $0.25.

Total estimated cost: Around $1 to $1.5/month.

Note: Prices may vary a bit depending on your AWS region and usage patterns.

Future Improvements & Ideas 🚀

Here are some optional enhancements you can build next:

  • ✅ Add a confirmation step before stopping services (with timeout)
  • ⏱ Schedule reminders if no reply is received after a prompt
  • 🔒 Add passphrase like secret+1 instead of plain numbers
  • 📊 Add status command to show running/stopped services
  • 📁 Add DynamoDB to track pending or delayed actions

Conclusion

Getting a “Can you start the QA server?” message at 1 AM is a guaranteed way to ruin your night’s sleep. That’s why I built this simple WhatsApp + AWS bot: so I don’t have to be the middleman anymore, and the devs and QA folks get exactly what they need — quick, secure control of ECS services right from their favorite chat app.

Whether you’re a small team trying to save cloud costs or just someone who values uninterrupted sleep (same here!), this little automation can go a long way in making DevOps more humane.

I hope you found this content informative and enjoyable. For more insightful blogs and updates, consider following and clicking the 👏 button below to show your support. Happy coding! 🚀

Thank you for reading! 💚

And they lived happily ever after 💓

Leave a Reply

Your email address will not be published. Required fields are marked *