Building a Serverless Customer Support Ticket Routing Service

In this blog post, we will build a serverless customer support ticket routing service using AWS services like Lambda, API Gateway, SNS, and SQS. This service enables authorized customers to submit support tickets through a REST API, which routes the tickets to the appropriate support team based on the customer group.

We will develop the service using the AWS Cloud Development Kit (CDK) and Python. Throughout this post, I will discuss various technical topics related to the development and implementation of the service.

Requirements and Application Architecture

We will build a serverless application on AWS that processes customer requests. Based on the customer tier, each request will be sent to either a priority or general channel, and all requests should be sent to the warehouse for future analysis.

The system must efficiently handle scaling and maintain high availability to ensure continuous service. It should guarantee no loss of notifications. Additionally, the system must incorporate robust security measures to protect against unauthorized access.

Now, we are ready to develop the initial diagram of our application. Here are key points to consider while designing our system architecture:

  • Authorizer: Our clients will send an authentication token in the header. The authorizer should authenticate the client and send the necessary client information to the router.
  • Router: The request should be routed to the appropriate queue based on the customer tier.
  • Scaling: The application should scale based on the number of requests we receive from customers.

Let’s create a simple diagram to represent all the components:

General Diagram

The architecture above includes several components: an authorizer, a transformation process for the request data that incorporates user information, and a router that directs requests to specific queues for processing. By using Amazon tools like API Gateway, AWS SNS, and AWS SQS, we can implement the solution with minimal code. Let’s take a look at the diagram below:

AWS Architecture Diagram

The API Gateway service handles request authorization, validation, and transformation. Using AWS service integration, the input data, including user information, will be sent to the SNS service, which will route the request to the appropriate queue using filtering.

  • API Gateway Authorizer: Authorizes a user and retrieves user information.
  • API Gateway Request Validation: Validates input requests based on the defined schema.
  • API Gateway Request Transformation: Adds user information to the initial request.
  • API Gateway Service Integration: Sends requests directly to an SNS topic.
  • SNS: Sends data to an SQS queue according to the filter policy.

AWS enables us to create solutions with minimal coding, allowing us to concentrate mainly on business logic. Now, let’s explore how to build the architecture described above using AWS CDK.

Building Application Infrastructure with AWS CDK and Python

We will use AWS CDK and Python to build the application infrastructure. Let’s create the API Gateway resource using the CDK.

api = apigw.RestApi(
    self,
    f"{construct_id}-rest-api",
    rest_api_name="Ticket Routing Rest API",
    description="The service handles user tickets /api/tickets",
    deploy_options=apigw.StageOptions(
        throttling_rate_limit=2,
        throttling_burst_limit=10,
    ),
)

Next, we will use a Lambda authorizer to control access to our service. When a client calls the service API, API Gateway invokes the Lambda authorizer. The Lambda authorizer must return an IAM policy that allows or denies access to the service API. Along with the IAM policy, the Lambda authorizer will include context with the necessary user information

To minimize the invocation of the Lambda function, we will use caching. This allows API Gateway to utilize the cached authorizer result instead of invoking the Lambda function each time.

lambda_authorizer_construct = LambdaConstruct(
    self,
    f"{construct_id}-authorizer",
    f"{construct_id}-authorizer",
    "authorizer",
    layers=[lambda_layer.layer],
)

authorizer = apigw.TokenAuthorizer(
    self,
    "request-authorizer",
    handler=lambda_authorizer_construct.lambda_function,
    # Read the header "Token" to get the token
    identity_source=apigw.IdentitySource.header("Token"),
    # The name of the authorizer
    authorizer_name="RequestAuthorizer",
    # The TTL of the cache
    results_cache_ttl=Duration.seconds(constants.AUTHORIZER_CACHE_TTL),
)

Now, let’s create an SNS topic with SQS queues to filter and route requests to specific queues

topic = sns.Topic(self, "TicketRouting", display_name="TicketRouting")

# Create SQS queues and lambda functions to process the queues
analytics_queue = sqs.Queue(
    self, "AnalyticsQueue", queue_name=f"{construct_id}-analytics"
)
priority_tickets_queue = sqs.Queue(
    self,
    "PriorityTicketsQueue",
    queue_name=f"{construct_id}-priority-tickets",
)
general_tickets_queue = sqs.Queue(
    self,
    "GeneralTicketsQueue",
    queue_name=f"{construct_id}-general-tickets"
)
# Subscribe the queues to the SNS topic with appropriate filter policies
# All tickets
topic.add_subscription(subs.SqsSubscription(analytics_queue))

# Only gold tier tickets
topic.add_subscription(
    subs.SqsSubscription(
        priority_tickets_queue,
        filter_policy={
            "customer_tier": sns.SubscriptionFilter.string_filter(allowlist=["gold"])
        },
    )
)
# General tickets
topic.add_subscription(
    subs.SqsSubscription(
        general_tickets_queue,
        filter_policy={
            "customer_tier": sns.SubscriptionFilter.string_filter(denylist=["gold"])
        },
    )
)

Let’s continue configuring our API Gateway. To validate a request, we will use API Gateway’s request validation feature. If validation fails, API Gateway will immediately reject the request and return a 400 error response to the caller. To perform the validation, we need to define a request model and create the validator.

ticket_create_model = api.add_model(
    "TicketCreateModel",
    content_type="application/json",
    schema=apigw.JsonSchema(
        schema=apigw.JsonSchemaVersion.DRAFT4,
        title="TicketCreateModel",
        type=apigw.JsonSchemaType.OBJECT,
        properties={
            "type": apigw.JsonSchemaType.STRING,
            "message": apigw.JsonSchemaType.STRING,
        },
        required=["message", "type"],
    ),
)

ticket_create_validator = apigw.RequestValidator(
    self,
    "TicketCreateValidator",
    rest_api=api,
    # the properties below are optional
    request_validator_name="ticket-create-validator",
    validate_request_body=True,
)

Before sending the request to SNS, we need to transform the request to include the user information added by the Lambda authorizer. We will use a mapping template to transform the request data. More detailed information can be found here

message_template = (
    '{"body": $input.json(\'$\'), '
    '"auth": {"user_id": "$context.authorizer.user_id", "email": "$context.authorizer.email", '
    '"tier": "$context.authorizer.customer_tier"}, '
    '"request_time_epoch": "$context.requestTimeEpoch", '
    '"request_id": "$context.requestId"}'
)

sns_request_template = (
    f"Action=Publish&"
    f"TopicArn=$util.urlEncode('{topic.topic_arn}')&"
    f"Message={message_template}&"
    f"MessageAttributes.entry.1.Name=customer_tier&"
    f"MessageAttributes.entry.1.Value.DataType=String&"
    f"MessageAttributes.entry.1.Value.StringValue=$context.authorizer.customer_tier"
)

Finally, we will create the API Gateway method and an AWS role that allows API Gateway to send data to SNS.

# Create an IAM role for API Gateway to publish to SNS
api_to_sns_role = iam.Role(
    self,
    "ApiToSnsRole",
    assumed_by=iam.ServicePrincipal("apigateway.amazonaws.com"),
)
topic.grant_publish(api_to_sns_role)

# Create the POST method with SNS integration
api_resource.add_method(
    "POST",
    apigw.AwsIntegration(
        service="sns",
        integration_http_method="POST",
        action="Publish",
        path="",
        options=apigw.IntegrationOptions(
            credentials_role=api_to_sns_role,
            passthrough_behavior=apigw.PassthroughBehavior.NEVER,
            request_parameters={
                "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'"
            },
            request_templates={
                "application/json": sns_request_template
            },
            integration_responses=[
                apigw.IntegrationResponse(
                    status_code="200",
                    response_templates={"application/json": '{"message": "Ticket created successfully"}'},
                    response_parameters={
                        "method.response.header.Content-Type": "'application/json'",
                        "method.response.header.Access-Control-Allow-Origin": "'*'",
                        "method.response.header.Access-Control-Allow-Credentials": "'true'",
                    },
                )
            ],
        ),
    ),
    authorizer=authorizer,
    request_models={"application/json": ticket_create_model},
    request_validator=ticket_create_validator,
    method_responses=[
        apigw.MethodResponse(
            status_code="200",
            response_parameters={
                "method.response.header.Content-Type": True,
                "method.response.header.Access-Control-Allow-Origin": True,
                "method.response.header.Access-Control-Allow-Credentials": True,
            },
        )
    ],
)

The code above represents the POST method, which receives requests from the user, validates them, transforms them, and sends them to SNS. The full code for the service can be found here .

Summary

The AWS API Gateway and AWS SNS are powerful services that make it easy to build serverless applications. In this post, I demonstrated how to use these tools to create a ticket routing service.

Related Posts

Building a REST API with AWS Lambda URLs, Python, and AWS CDK

Introduction AWS Lambda is a powerful serverless platform ideal for building small-scale REST services. There are three common methods to create a REST API with an AWS Lambda function: API Gateway, Application Load Balancer, and Lambda URLs (I’m not going to compare them here, but each has its pros and cons).

Read more

How to Setup, Deploy, and Observe AWS Lambda Function in a Microservice Architecture

Introduction AWS Lambda is an excellent tool for building microservices due to its scalability, cost-efficiency, and maintainability. However, setting up, structuring, and monitoring a Lambda Function can be challenging.

Read more

Never Lose Your Data: Automated MacBook Backups with Restic and Real-Time Monitoring via CloudWatch

We rely on our MacBooks for everything—from crucial work projects and irreplaceable photos to vital financial records. The thought of losing it all—due to a crash, theft, or accidental deletion—is truly unsettling.

Read more