{"id":78607,"date":"2026-07-29T13:17:01","date_gmt":"2026-07-29T07:47:01","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=78607"},"modified":"2026-07-30T15:54:49","modified_gmt":"2026-07-30T10:24:49","slug":"slack-escalation-bot-building-a-zero-timeout-ticket-system-with-aws-lambda-and-sns","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/slack-escalation-bot-building-a-zero-timeout-ticket-system-with-aws-lambda-and-sns\/","title":{"rendered":"Slack Escalation Bot: Building a Zero-Timeout Ticket System with AWS Lambda and SNS"},"content":{"rendered":"<h1>Introduction<\/h1>\n<p>Imagine a client&#8217;s website fails at midnight. Your team is on Slack, and raising a support ticket involves opening a browser, logging into Fresh service, filling out a form, and waiting for it to be assigned. In an urgent situation, every extra step adds unnecessary delay.<\/p>\n<p>so to simplify this, we created a Slack slash command called <strong>\/escalate<\/strong>. and by using this anyone can raise an urgent support ticket directly from Slack by typing a single command, ticket will be created in Fresh Service automatically with the details like requester&#8217;s name, timestamp, and channel.<\/p>\n<p>In this article, we will walk through the complete implementation using AWS Lambda and Amazon SNS, explain how the Slack integration works, and break down the production costs of running the solution.<\/p>\n<h1>What Is the Slack Escalation Bot?<\/h1>\n<p>The Slack Escalation Bot is a slash command integration that allows anyone to raise urgent support tickets directly from slack channel. Instead creating a ticket in Fresh Service by simply typing :<\/p>\n<pre>\/escalate Client ABC payment gateway is completely down<\/pre>\n<p>Within seconds, the following happens:<\/p>\n<ol>\n<li>Slack sends the command to an AWS Lambda function via HTTPS<\/li>\n<li>Lambda validates the request and publishes the message to an AWS SNS topic<\/li>\n<li>SNS sends a structured email to all subscribed recipients including the Fresh Service inbound email address<\/li>\n<li>Fresh Service automatically creates a support ticket from the incoming email<\/li>\n<li>A confirmation message is posted in the Slack channel visible to everyone<\/li>\n<\/ol>\n<p>The entire flow from typing the command to the ticket being created, completes in under 300 milliseconds.<\/p>\n<h1>Architecture Overview<\/h1>\n<p>The system consists of three components:<\/p>\n<ul>\n<li><strong>Slack Slash Command<\/strong> &#8211; The user-facing trigger, configured within the Slack app.<\/li>\n<li><strong>AWS Lambda<\/strong> &#8211; a serverless Python function that receives the Slack request, validates it, and publishes to SNS.<\/li>\n<li><strong>AWS SNS (Simple Notification Service)<\/strong> &#8211; a topic with email subscriptions which delivers the email to Fresh Service<\/li>\n<\/ul>\n<p>The flow looks like this:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\" wp-image-78608\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/03\/Gemini_Generated_Image_o1f4oho1f4oho1f4.png\" alt=\"flow\" width=\"664\" height=\"362\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/03\/Gemini_Generated_Image_o1f4oho1f4oho1f4.png 1408w, \/blog\/wp-ttn-blog\/uploads\/2026\/03\/Gemini_Generated_Image_o1f4oho1f4oho1f4-300x164.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/03\/Gemini_Generated_Image_o1f4oho1f4oho1f4-1024x559.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/03\/Gemini_Generated_Image_o1f4oho1f4oho1f4-768x419.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/03\/Gemini_Generated_Image_o1f4oho1f4oho1f4-624x340.png 624w\" sizes=\"(max-width: 664px) 100vw, 664px\" \/><\/p>\n<h1>Why Slack&#8217;s 3-Second Limit Matters<\/h1>\n<p>Slack requires every slash command endpoint to respond within <strong>3000 milliseconds<\/strong>. If there is no response within the time, Slack shows the user:<\/p>\n<pre>\/escalate failed with the error \"operation_timeout\"<\/pre>\n<p>so the architecture involves a two phase response pattern:<\/p>\n<ol>\n<li><strong>Phase 1 &#8211; Immediate ACK:<\/strong> Lambda instantly returns <code>\u23f3 Raising your escalation ticket...<\/code> to Slack visible only to the user who typed the command. This happens in milliseconds.<\/li>\n<li><strong>Phase 2 &#8211; Channel Confirmation:<\/strong> After SNS publishes the email, Lambda POSTs the full confirmation back to the channel via Slack&#8217;s response_url visible to everyone in the channel.<\/li>\n<\/ol>\n<h1>Step-by-Step Setup<\/h1>\n<h2>Step 1 Create the SNS Topic<\/h2>\n<ol>\n<li>Open <strong>AWS Console <\/strong>goto<strong> SNS &#8211;&gt; Topics &#8211;&gt; Create Topic.<\/strong><\/li>\n<li>Now create a Standard topic named slack-escalations.<\/li>\n<li>Click on <strong>Create Topic<\/strong> and copy the <strong>Topic ARN.<\/strong><\/li>\n<li>Inside the topic, click on <strong>Create Subscription.<\/strong><\/li>\n<li>Enter your Fresh service inbound email address.<\/li>\n<li>Repeat for any additional recipients.<\/li>\n<\/ol>\n<p><strong>Note:<\/strong> If we want to add new recipient in the future, simply add a new SNS subscription in the AWS Console. No code changes or Lambda redeployment is needed.<\/p>\n<h2>Step 2\u00a0 Create the Lambda Function<\/h2>\n<ol>\n<li>Open AWS Console and goto\u2192 Lambda \u2192 Create Function<\/li>\n<li>Name: slack-escalation, Runtime: Python 3.12<\/li>\n<li>Click on <strong>Create Function<\/strong><\/li>\n<li>Paste the following code and click <strong>Deploy<\/strong><\/li>\n<\/ol>\n<pre>import json\r\nimport boto3\r\nimport urllib.parse\r\nimport urllib.request\r\nimport base64\r\nfrom datetime import datetime\r\n\r\n# \u2500\u2500 CONFIG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nSNS_TOPIC_ARN  = \"arn:aws:sns:ap-south-1:YOUR_ACCOUNT_ID:slack-escalations\"\r\nSUBJECT_PREFIX = \"Escalation from Frequency\"\r\nsns            = boto3.client(\"sns\", region_name=\"ap-south-1\")\r\n\r\ndef lambda_handler(event, context):\r\n    try:\r\n        # Parse Slack POST body \u2014 handle base64 encoding from Function URL\r\n        raw = event.get(\"body\", \"\")\r\n        if event.get(\"isBase64Encoded\", False):\r\n            raw = base64.b64decode(raw).decode(\"utf-8\")\r\n\r\n        body     = urllib.parse.parse_qs(raw, keep_blank_values=True)\r\n        text     = body.get(\"text\",         [\"\"])[0].strip()\r\n        user     = body.get(\"user_name\",    [\"unknown\"])[0]\r\n        user_id  = body.get(\"user_id\",      [\"\"])[0]\r\n        channel  = body.get(\"channel_name\", [\"unknown\"])[0]\r\n        resp_url = body.get(\"response_url\", [\"\"])[0]\r\n\r\n        # \u2500\u2500 Validate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        if not text:\r\n            return ok(\"\u26a0\ufe0f No message. Usage: `\/escalate Your issue here`\", True)\r\n        if len(text) &gt; 1000:\r\n            return ok(\"\u26a0\ufe0f Message too long (max 1000 chars).\", True)\r\n\r\n        # \u2500\u2500 Build and publish to SNS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        timestamp = datetime.now().strftime(\"%d %b %Y, %I:%M %p IST\")\r\n        sns.publish(\r\n            TopicArn = SNS_TOPIC_ARN,\r\n            Subject  = SUBJECT_PREFIX + \": \" + text[:60],\r\n            Message  = (\r\n                \"A new escalation has been raised via Slack.\\n\\n\"\r\n                \"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\"\r\n                \"ESCALATION DETAILS\\n\"\r\n                \"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\"\r\n                f\"Issue     : {text}\\n\"\r\n                f\"Raised By : @{user} (Slack ID: {user_id})\\n\"\r\n                f\"Channel   : #{channel}\\n\"\r\n                f\"Time (IST): {timestamp}\\n\"\r\n                \"Source    : Slack \/escalate command\\n\"\r\n                \"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n\"\r\n                \"This ticket was automatically created from the Frequency Slack workspace.\"\r\n            )\r\n        )\r\n\r\n        # \u2500\u2500 Post confirmation back to Slack channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        if resp_url:\r\n            urllib.request.urlopen(urllib.request.Request(\r\n                resp_url,\r\n                data    = json.dumps({\r\n                    \"response_type\": \"in_channel\",\r\n                    \"text\": (\r\n                        \"\ud83d\udea8 *Escalation ticket created!*\\n\"\r\n                        f\"\ud83d\udc64 *Raised by:* @{user} in #{channel}\\n\"\r\n                        f\"\ud83d\udcdd *Issue:* {text}\"\r\n                    )\r\n                }).encode(\"utf-8\"),\r\n                headers = {\"Content-Type\": \"application\/json\"},\r\n                method  = \"POST\"\r\n            ))\r\n\r\n        # \u2500\u2500 Immediate ACK to Slack \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        return ok(\"\u23f3 Raising your escalation ticket...\", True)\r\n\r\n    except Exception as err:\r\n        print(\"ERROR:\", str(err))\r\n        return ok(f\"\u274c Failed: {str(err)}\", True)\r\n\r\n\r\ndef ok(text, ephemeral):\r\n    return {\r\n        \"statusCode\": 200,\r\n        \"headers\": {\"Content-Type\": \"application\/json\"},\r\n        \"body\": json.dumps({\r\n            \"response_type\": \"ephemeral\" if ephemeral else \"in_channel\",\r\n            \"text\": text\r\n        })\r\n    }\r\n<\/pre>\n<p>After deploying, open the Configuration goto General configuration and Edit, \u00a0set the Timeout to 10 seconds. Although Slack expects a response within three seconds, a slightly longer Lambda timeout helps prevent failures during temporary AWS delays.<\/p>\n<h2>Step 3 Grant Lambda Permission to Use SNS<\/h2>\n<ol>\n<li>Goto Lambda \u2192 <strong>Configuration \u2192 Permissions<\/strong> \u2192 click the Role name link<\/li>\n<li>IAM opens \u2192 <strong>Add Permissions \u2192 Attach Policies<\/strong><\/li>\n<li>Search AmazonSNSFullAccess \u2192 Add<\/li>\n<\/ol>\n<h2>Step 4 Enable Lambda Function URL<\/h2>\n<ol>\n<li>Goto Lambda \u2192 your function \u2192 <strong>Configuration \u2192 Function URL \u2192 Create function URL<\/strong><\/li>\n<li>Auth type: <strong>NONE<\/strong> Slack must reach it publicly without authentication<\/li>\n<li>Click <strong>Save<\/strong> and copy the URL<\/li>\n<\/ol>\n<p>The URL format is: <code>https:\/\/abc123xyz.lambda-url.ap-south-1.on.aws\/<br \/>\n<\/code>This is your Slack Request URL. No API Gateway is needed.<\/p>\n<h2>Step 5 Configure the Slack Slash Command<\/h2>\n<ol>\n<li>Go to <strong>api.slack.com\/apps<\/strong> \u2192 your Slack app \u2192 <strong>Slash Commands \u2192 Edit<\/strong><\/li>\n<li>Fill in the following:<\/li>\n<\/ol>\n<ul>\n<li><strong>Command:<\/strong> <code>\/escalate<\/code><\/li>\n<li><strong>Request URL:<\/strong> Your Lambda Function URL from Step 4<\/li>\n<li><strong>Short Description:<\/strong> Raise an urgent escalation ticket to the support team<\/li>\n<li><strong>Usage Hint:<\/strong> Describe the issue briefly for example Client XYZ portal is down<\/li>\n<li><strong>Escape channels, users, and links:<\/strong> OFF leave unchecked<\/li>\n<\/ul>\n<p>Click on <strong>Save<\/strong>.<\/p>\n<h2>Step 6 Testing the Slash Command<\/h2>\n<p>Open any Slack channel and type:<\/p>\n<pre>\/escalate Client ABC payment gateway is completely down<\/pre>\n<p>Expected response will be :<\/p>\n<ul>\n<li><strong>Immediately (visible only to you):<\/strong> \u23f3 Raising your escalation ticket&#8230;<\/li>\n<li><strong>1\u20132 seconds later (visible to whole channel):<\/strong><\/li>\n<\/ul>\n<pre>\ud83d\udea8 Escalation ticket created!\r\n\ud83d\udc64 Raised by: @ritika.chauhan in #new-test\r\n\ud83d\udcdd Issue: Client ABC payment gateway is completely down\r\n<\/pre>\n<p>The email that appears in FreshService looks like this:<\/p>\n<pre>Subject: Escalation from Frequency: Client ABC payment gateway is completely down\r\n\r\nA new escalation has been raised via Slack.\r\n\r\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\r\nESCALATION DETAILS\r\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\r\nIssue     : Client ABC payment gateway is completely down\r\nRaised By : @ritika.chauhan (Slack ID: U074ZHMQCKV)\r\nChannel   : #new-test\r\nTime (IST): 05 Mar 2026, 02:39 PM IST\r\nSource    : Slack \/escalate command\r\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\r\n\r\nThis ticket was automatically created from the Frequency Slack workspace.\r\n<\/pre>\n<h1>Cost Analysis<\/h1>\n<p>All three AWS services\u00a0 Lambda, Lambda Function URL, and SNS\u00a0 fall within the <strong>AWS Free Tier which never expires<\/strong>. Below is the full breakdown:<\/p>\n<table border=\"1\" cellspacing=\"0\" cellpadding=\"8\">\n<thead>\n<tr>\n<th>Slash Commands \/ Month<\/th>\n<th>Lambda Requests Cost<\/th>\n<th>Lambda Compute Cost<\/th>\n<th>SNS Publish Cost<\/th>\n<th>Total Estimated Cost<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>10<\/td>\n<td>$0.000002<\/td>\n<td>$0.000004<\/td>\n<td>$0.000005<\/td>\n<td><strong>~$0.00001<\/strong><\/td>\n<\/tr>\n<tr>\n<td>25<\/td>\n<td>$0.000005<\/td>\n<td>$0.000010<\/td>\n<td>$0.000013<\/td>\n<td><strong>~$0.00003<\/strong><\/td>\n<\/tr>\n<tr>\n<td>50<\/td>\n<td>$0.000010<\/td>\n<td>$0.000021<\/td>\n<td>$0.000025<\/td>\n<td><strong>~$0.00006<\/strong><\/td>\n<\/tr>\n<tr>\n<td>100<\/td>\n<td>$0.000020<\/td>\n<td>$0.000042<\/td>\n<td>$0.000050<\/td>\n<td><strong>~$0.00011<\/strong><\/td>\n<\/tr>\n<tr>\n<td>1,000,000<\/td>\n<td>$0.20<\/td>\n<td>~$0.42<\/td>\n<td>$0.50<\/td>\n<td><strong>~$1.12 total<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Even at 1,000,000 slash commands per month, roughly 33,000 per day the total cost is $1.12. At realistic usage of tens to hundreds per month, the cost rounds to <strong>$0.00<\/strong>.<\/p>\n<p>Free tier limits per service:<\/p>\n<ul>\n<li><strong>Lambda:<\/strong> 1 million requests and 400,000 GB-seconds per month &#8211; free forever<\/li>\n<li><strong>Lambda Function URL:<\/strong> Included within Lambda free tier &#8211; no separate charge<\/li>\n<li><strong>SNS Email:<\/strong> 1,000 email notifications per month &#8211; free forever<\/li>\n<\/ul>\n<h1>Conclusion<\/h1>\n<p>The Slack Escalation Bot is a reliable, production-ready tool. With the Slack Escalation Bot, the team can raise urgent support tickets within 300 milliseconds without manually opening FreshService. No code changes are required to add new recipients, just a new SNS subscription.If your team is on Slack and the team uses a helpdesk that supports inbound email ticket creation, this architecture works.\u00a0 Adding new commands follows the same Lambda pattern and can be extended to create tickets in Jira, PagerDuty, or any system with an API.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Imagine a client&#8217;s website fails at midnight. Your team is on Slack, and raising a support ticket involves opening a browser, logging into Fresh service, filling out a form, and waiting for it to be assigned. In an urgent situation, every extra step adds unnecessary delay. so to simplify this, we created a Slack [&hellip;]<\/p>\n","protected":false},"author":1873,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":2},"categories":[5877],"tags":[248,3812],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/78607"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1873"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=78607"}],"version-history":[{"count":3,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/78607\/revisions"}],"predecessor-version":[{"id":80907,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/78607\/revisions\/80907"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=78607"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=78607"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=78607"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}