Artificial Intelligence

Automating customer retention workflows in Amazon Quick

Automating customer retention workflows in Amazon Quick can turn a five-day churn-response cycle into one that takes minutes. Last quarter, a mid-size SaaS company lost 12% of its at-risk accounts because the retention team took five days to identify and contact dissatisfied customers. By the time someone manually reviewed CSAT spreadsheets and call transcripts, those customers had already churned. Amazon Quick shortens that response window from days to minutes.

This post walks through building an automated customer retention pipeline in Amazon Quick. The pipeline detects dissatisfied customers from structured data. In addition, it analyzes sentiment from call transcripts and scores customers by retention priority. It then generates retention offers tailored to each situation. In this post, you learn how to:

  • Configure a Quick Space with contact center datasets and call transcripts.
  • Create and register a custom MCP Action for customer scoring.
  • Build a Chat Agent that combines quantitative key performance indicators (KPIs) with qualitative transcript analysis.
  • Convert the analysis into a reusable Quick Flow.
  • Orchestrate the full pipeline with Amazon Quick Automate.

Solution overview

The retention pipeline connects Amazon Quick components in sequence. Quick Chat Agent uses natural language to query structured data and unstructured call transcripts. It combines quantitative scores with qualitative sentiment signals. This surfaces why a customer is at risk, not just that they are.

Amazon Quick Flows turns the repeatable Chat-based analysis into a scheduled or on-demand automation that runs without manual intervention. Its final step formats the results as a structured list of at-risk customers that downstream automation can consume directly.

Quick Automate executes a multi-step pipeline. It utilizes the at-risk customer list that the Flow produces and scores those customers through a custom MCP Action. An MCP Action is a serverless endpoint that extends Quick Automate with custom business logic. Refer to the Quick Automate MCP developer guide for details. The pipeline then ranks customers by priority, generates retention letters, and uploads them to Amazon Simple Storage Service (Amazon S3) for distribution.

You can run the workflow on AWS infrastructure and can access audit trails, breakpoints, and role-based access controls.

Automate pipeline architecture

The pipeline executes sequential steps. It downloads the list of at-risk customers(output of Flow) and structured contact center dataset from Amazon Simple Storage Service (Amazon S3). A scoring step ranks those customers by retention priority (based on CSAT and how recently each customer had an issue) and picks the top two highest-priority cases. The pipeline then drafts bonus-credit letters that reference each customer’s specific issues, saves them as a PDF, and uploads the finished letters to Amazon S3 for delivery and archival.

The following table summarizes each step:

Step Action Details
1 Download at-risk customers and contact center dataset Download the at-risk customers file (Negative Sentiment.doc) as well as the structured dataset contact_center_data.csv from Amazon S3.
2 Retention scoring Score each customer using lifetime value through the custom MCP Action, and pick the top two highest-priority customers.
3 Retention letter generation Draft bonus-credit letters that reference each customer’s specific issues.
4 PDF creation Save the generated letters as a PDF document.
5 Upload to Amazon S3 Store the generated letters in Amazon S3 for delivery and archival.

This solution uses Amazon S3, Amazon Quick (Spaces, Chat, Flows, Automate), and custom MCP Actions.

Prerequisites

To follow this walk-through, you need:

  • An AWS account with Amazon Quick activated (Chat, Flows, and Automate)
  • Amazon Quick Admin Access and Command Line Interface(CLI) access
  • A fictitious structured contact center dataset in CSV format named contact_center_data.csv (columns: customer_id, csat_score, timestamp, call_reason, duration_seconds etc).
  • A fictitious Call transcript documents named Call_transcripts.docx contain exact transcripts.
  • An Amazon S3 bucket with two folders named “input” and “output” for pipeline input and output (example: amzn-s3-demo-bucket). Upload the contact_center_data.csv directly under the S3 bucket.
    Please note S3 bucket names are globally unique. Choose an appropriate name for the bucket 

Solution walkthrough

The following sections walk through the build end to end, from preparing your Quick assets and registering the MCP Action connector to assembling the Chat Agent, Flow, and Automate workflow. Complete them in order, because each step builds on the components created before it.

To prepare your Quick Dataset

  1. From the Amazon Quick menu, select Data
  2. Select Create dataset.
  3. For this solution we will be using the contact_center_data.csv file outlined in the prerequisites section, so select Upload file to upload the file.
  4. The windows explorer windows opens up. Select and chose this csv and click on ‘open‘.
  5. After adding the csv, you will be presented with a box showing a preview of the chosen dataset, select Next.
  6. In the screen that follows, click on Edit/Preview data
  7. This takes you to the “edit” mode of the dataset, rename the dataset as “contact_center_data”, then look at the data preview to make sure everything looks right before selecting Save and publish

To prepare your Quick Space

A Quick Space is the data layer that gives the Chat Agent access to both structured datasets (for filtering and aggregation) and unstructured documents (for sentiment analysis). Creating the Space first helps verify that every downstream component draws from the same source of truth.

  1. From the Quick console, select Spaces, then select Create space.

Amazon Quick console with Spaces selected

  1. For Space name, enter ContactSpace.

Create space dialog with the space name set to ContactSpace

  1. In the screen that follows, select Datasets.

  1. Then select Add datasets. Search for and select the checkbox next to your contact_center_data dataset, then choose Add.

Add datasets panel with the contact_center_data dataset selected

  1. Select File Uploads, then select Upload Files, and upload your Call_transcripts.docx transcript file.

  1. Wait for indexing to complete. Once completed, the Status column should show Ready for both the dataset and the document.

Space data sources showing Ready status for the dataset and document

To create the MCP Action connector

An MCP Action connector bridges Amazon Quick and your custom business logic. The Model Context Protocol (MCP) is an open standard that lets AI assistants such as Amazon Quick call external tools in a consistent request-and-response format. In this walkthrough, you implement the MCP server as an AWS Lambda function (which runs your scoring logic without provisioning servers), expose it through Amazon API Gateway (which gives it a public web address), and register that endpoint in Amazon Quick.

The following Lambda function implements a minimal MCP server. It handles the three JSON-RPC methods Amazon Quick calls during connection: initialize (the handshake), tools/list (which advertises the score_customers tool so Amazon Quick can discover it), and tools/call (which runs the scoring when the tool is invoked). The tool’s inputSchema follows JSON Schema Draft 7, which Amazon Quick requires at publish time:



"""
Customer Scoring MCP Server - Lambda Handler
Scores frustrated customers and identifies top performers for bonuses
"""
import json
from typing import List, Dict, Any

def lambda_handler(event, context):
    """Handle MCP requests for customer scoring"""
    
    # Parse MCP request
    body = json.loads(event['body']) if isinstance(event.get('body'), str) else event.get('body', {})
    
    method = body.get('method')
    request_id = body.get('id')
    params = body.get('params', {})
    
    # Handle MCP methods
    if method == 'initialize':
        return mcp_response(request_id, {
            'protocolVersion': '2024-11-05',
            'capabilities': {'tools': {}},
            'serverInfo': {
                'name': 'customer-scoring-mcp',
                'version': '1.0.0'
            }
        })
    
    elif method == 'tools/list':
        return mcp_response(request_id, {
            'tools': [
                {
                    'name': 'calculate_customer_scores',
                    'description': 'Calculate customer loyalty scores based on multiple factors',
                    'inputSchema': {
                        'type': 'object',
                        'properties': {
                            'customers': {
                                'type': 'array',
                                'description': 'List of customer records with metrics',
                                'items': {
                                    'type': 'object',
                                    'properties': {
                                        'customer_id': {'type': 'string'},
                                        'customer_name': {'type': 'string'},
                                        'total_purchases': {'type': 'number'},
                                        'account_age_months': {'type': 'number'},
                                        'frustration_incidents': {'type': 'number'},
                                        'avg_purchase_value': {'type': 'number'},
                                        'support_tickets': {'type': 'number'}
                                    }
                                }
                            },
                            'weights': {
                                'type': 'object',
                                'description': 'Scoring weights (optional)',
                                'properties': {
                                    'purchases': {'type': 'number', 'default': 0.3},
                                    'account_age': {'type': 'number', 'default': 0.2},
                                    'frustration_penalty': {'type': 'number', 'default': 0.2},
                                    'purchase_value': {'type': 'number', 'default': 0.2},
                                    'support_penalty': {'type': 'number', 'default': 0.1}
                                }
                            }
                        },
                        'required': ['customers']
                    }
                },
                {
                    'name': 'get_top_customers',
                    'description': 'Get top N customers by score for bonus allocation',
                    'inputSchema': {
                        'type': 'object',
                        'properties': {
                            'scored_customers': {
                                'type': 'array',
                                'description': 'List of customers with scores'
                            },
                            'top_n': {
                                'type': 'integer',
                                'description': 'Number of top customers to return',
                                'default': 2
                            },
                            'bonus_amount': {
                                'type': 'number',
                                'description': 'Bonus amount per customer',
                                'default': 100
                            }
                        },
                        'required': ['scored_customers']
                    }
                },
                {
                    'name': 'apply_scoring_rules',
                    'description': 'Apply custom business rules to customer data',
                    'inputSchema': {
                        'type': 'object',
                        'properties': {
                            'customers': {
                                'type': 'array',
                                'description': 'Customer data'
                            },
                            'rules': {
                                'type': 'object',
                                'description': 'Custom scoring rules',
                                'properties': {
                                    'min_purchases': {'type': 'number'},
                                    'min_account_age': {'type': 'number'},
                                    'max_frustration': {'type': 'number'},
                                    'bonus_for_loyalty': {'type': 'number'}
                                }
                            }
                        },
                        'required': ['customers', 'rules']
                    }
                }
            ]
        })
    
    elif method == 'tools/call':
        tool_name = params.get('name')
        arguments = params.get('arguments', {})
        
        if tool_name == 'calculate_customer_scores':
            result = calculate_customer_scores(arguments)
        elif tool_name == 'get_top_customers':
            result = get_top_customers(arguments)
        elif tool_name == 'apply_scoring_rules':
            result = apply_scoring_rules(arguments)
        else:
            return mcp_error(request_id, -32601, f'Tool not found: {tool_name}')
        
        return mcp_response(request_id, {
            'content': [
                {
                    'type': 'text',
                    'text': json.dumps(result, indent=2)
                }
            ]
        })
    
    else:
        return mcp_error(request_id, -32601, f'Method not found: {method}')


def calculate_customer_scores(arguments: Dict[str, Any]) -> Dict[str, Any]:
    """
    Calculate customer loyalty scores based on call center data
    
    Scoring factors from call center dataset:
    - Call frequency (engagement level)
    - Resolution quality (first_call_resolution, resolution_status)
    - CSAT scores (customer satisfaction)
    - Efficiency (low queue time, low transfers, low hold time)
    - Issue severity (complaints vs general inquiries)
    """
    customers = arguments.get('customers', [])
    weights = arguments.get('weights', {})
    
    # Default weights optimized for call center data
    w_engagement = weights.get('engagement', 0.25)  # Call frequency
    w_satisfaction = weights.get('satisfaction', 0.30)  # CSAT scores
    w_resolution = weights.get('resolution', 0.20)  # FCR and resolution quality
    w_efficiency = weights.get('efficiency', 0.15)  # Low transfers, hold time
    w_issue_severity = weights.get('issue_severity', 0.10)  # Complaint vs inquiry ratio
    
    scored_customers = []
    
    for customer in customers:
        # Extract metrics from call center data
        total_calls = customer.get('total_calls', 0)
        avg_csat = customer.get('avg_csat_score', 0)
        fcr_rate = customer.get('first_call_resolution_rate', 0)  # Percentage
        avg_transfers = customer.get('avg_transfer_count', 0)
        avg_hold_time = customer.get('avg_hold_time_seconds', 0)
        complaint_ratio = customer.get('complaint_ratio', 0)  # Complaints / total calls
        resolved_rate = customer.get('resolved_rate', 0)  # Percentage
        
        # 1. Engagement Score (0-100): More calls = more engaged
        # Cap at 20 calls for normalization
        engagement_score = min((total_calls / 20) * 100, 100)
        
        # 2. Satisfaction Score (0-100): Based on CSAT (1-5 scale)
        # Convert CSAT to 0-100 scale
        satisfaction_score = (avg_csat / 5) * 100 if avg_csat > 0 else 0
        
        # 3. Resolution Score (0-100): FCR + resolved rate
        resolution_score = (fcr_rate * 0.6 + resolved_rate * 0.4)
        
        # 4. Efficiency Score (0-100): Penalize high transfers and hold time
        # Low transfers = good (0-1 transfers = 100, 3+ = 0)
        transfer_score = max(100 - (avg_transfers * 33), 0)
        # Low hold time = good (0-300s = 100, 600s+ = 0)
        hold_score = max(100 - (avg_hold_time / 6), 0)
        efficiency_score = (transfer_score * 0.6 + hold_score * 0.4)
        
        # 5. Issue Severity Score (0-100): Lower complaint ratio = better
        # 0% complaints = 100, 50%+ complaints = 0
        issue_severity_score = max(100 - (complaint_ratio * 200), 0)
        
        # Calculate weighted total score
        total_score = (
            engagement_score * w_engagement +
            satisfaction_score * w_satisfaction +
            resolution_score * w_resolution +
            efficiency_score * w_efficiency +
            issue_severity_score * w_issue_severity
        )
        
        # Determine customer tier
        if total_score >= 80:
            tier = 'Platinum'
        elif total_score >= 65:
            tier = 'Gold'
        elif total_score >= 50:
            tier = 'Silver'
        else:
            tier = 'Bronze'
        
        scored_customers.append({
            'customer_id': customer.get('customer_id'),
            'loyalty_score': round(total_score, 2),
            'tier': tier,
            'score_breakdown': {
                'engagement': round(engagement_score * w_engagement, 2),
                'satisfaction': round(satisfaction_score * w_satisfaction, 2),
                'resolution': round(resolution_score * w_resolution, 2),
                'efficiency': round(efficiency_score * w_efficiency, 2),
                'issue_severity': round(issue_severity_score * w_issue_severity, 2)
            },
            'metrics': {
                'total_calls': total_calls,
                'avg_csat_score': avg_csat,
                'fcr_rate': fcr_rate,
                'resolved_rate': resolved_rate,
                'avg_transfer_count': avg_transfers,
                'avg_hold_time_seconds': avg_hold_time,
                'complaint_ratio': complaint_ratio
            },
            'risk_flags': {
                'high_transfers': avg_transfers >= 2,
                'low_csat': avg_csat < 3,
                'high_complaints': complaint_ratio > 0.3,
                'poor_resolution': resolved_rate < 70
            }
        })
    
    # Sort by score descending
    scored_customers.sort(key=lambda x: x['loyalty_score'], reverse=True)
    
    return {
        'scored_customers': scored_customers,
        'total_customers': len(scored_customers),
        'avg_score': round(sum(c['loyalty_score'] for c in scored_customers) / len(scored_customers), 2) if scored_customers else 0,
        'tier_distribution': {
            'Platinum': len([c for c in scored_customers if c['tier'] == 'Platinum']),
            'Gold': len([c for c in scored_customers if c['tier'] == 'Gold']),
            'Silver': len([c for c in scored_customers if c['tier'] == 'Silver']),
            'Bronze': len([c for c in scored_customers if c['tier'] == 'Bronze'])
        }
    }


def get_top_customers(arguments: Dict[str, Any]) -> Dict[str, Any]:
    """Get top N customers for bonus allocation"""
    scored_customers = arguments.get('scored_customers', [])
    top_n = arguments.get('top_n', 2)
    bonus_amount = arguments.get('bonus_amount', 100)
    
    # Sort by score if not already sorted
    sorted_customers = sorted(scored_customers, key=lambda x: x.get('loyalty_score', 0), reverse=True)
    
    # Get top N
    top_customers = sorted_customers[:top_n]
    
    # Add bonus information
    for i, customer in enumerate(top_customers):
        customer['rank'] = i + 1
        customer['bonus_amount'] = bonus_amount
        customer['bonus_reason'] = f"Top {i+1} loyal customer despite frustration incidents"
    
    return {
        'top_customers': top_customers,
        'total_bonus_allocated': bonus_amount * len(top_customers),
        'selection_criteria': f'Top {top_n} by loyalty score'
    }


def apply_scoring_rules(arguments: Dict[str, Any]) -> Dict[str, Any]:
    """Apply custom business rules to filter and score customers"""
    customers = arguments.get('customers', [])
    rules = arguments.get('rules', {})
    
    min_purchases = rules.get('min_purchases', 0)
    min_account_age = rules.get('min_account_age', 0)
    max_frustration = rules.get('max_frustration', 999)
    bonus_for_loyalty = rules.get('bonus_for_loyalty', 10)
    
    qualified_customers = []
    disqualified_customers = []
    
    for customer in customers:
        disqualification_reasons = []
        
        # Check rules
        if customer.get('total_purchases', 0) < min_purchases:
            disqualification_reasons.append(f"Purchases below minimum ({min_purchases})")
        
        if customer.get('account_age_months', 0) < min_account_age:
            disqualification_reasons.append(f"Account age below minimum ({min_account_age} months)")
        
        if customer.get('frustration_incidents', 0) > max_frustration:
            disqualification_reasons.append(f"Too many frustration incidents (>{max_frustration})")
        
        if disqualification_reasons:
            disqualified_customers.append({
                'customer_id': customer.get('customer_id'),
                'customer_name': customer.get('customer_name'),
                'reasons': disqualification_reasons
            })
        else:
            # Apply loyalty bonus
            base_score = customer.get('loyalty_score', 0)
            adjusted_score = base_score + bonus_for_loyalty
            
            qualified_customers.append({
                **customer,
                'adjusted_score': adjusted_score,
                'loyalty_bonus_applied': bonus_for_loyalty,
                'qualified': True
            })
    
    return {
        'qualified_customers': qualified_customers,
        'disqualified_customers': disqualified_customers,
        'qualification_rate': f"{len(qualified_customers)}/{len(customers)} ({round(len(qualified_customers)/len(customers)*100, 1)}%)" if customers else "0%",
        'rules_applied': rules
    }


def mcp_response(request_id, result):
    """Format MCP success response"""
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'POST, OPTIONS',
            'Access-Control-Allow-Headers': 'Content-Type'
        },
        'body': json.dumps({
            'jsonrpc': '2.0',
            'id': request_id,
            'result': result
        })
    }


def mcp_error(request_id, code, message):
    """Format MCP error response"""
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps({
            'jsonrpc': '2.0',
            'id': request_id,
            'error': {
                'code': code,
                'message': message
            }
        })
    }

Deploy this function to AWS Lambda:
  1. Open the AWS Lambda console and choose Create function. Select Author from scratch.
  2. For Function name, enter customer-scoring. For Runtime, choose a Python version 3.12. Choose Create function.
  3. In the Code tab, replace the default code with the function shown earlier, then choose Deploy.

With the function deployed, create an API Gateway endpoint in front of it using the following AWS CLI commands. The easiest way to run these is in AWS CloudShell — search for and select CloudShell in the AWS console, and the AWS CLI is already available with your account credentials. Run the commands in order — each one produces a value (such as the API ID) that you use in the next.

Replace the placeholder values (<API_ID>, <REGION>, <ACCOUNT_ID>, <LAMBDA_FUNCTION_NAME>) with your own


# ============================================
# API Gateway + Lambda: mcp_cust_score
# Single script — no manual steps
# ============================================

# STEP 1: Create REST API (single call, parse both IDs)
OUTPUT=$(aws apigateway create-rest-api \
  --name mcp-cust-score-api \
  --endpoint-configuration types=REGIONAL \
  --output json)

API_ID=$(echo $OUTPUT | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
ROOT_ID=$(echo $OUTPUT | python3 -c "import sys,json; print(json.load(sys.stdin)['rootResourceId'])")

echo "API_ID: $API_ID"
echo "ROOT_ID: $ROOT_ID"

# STEP 2: Create /mcp resource
MCP_ID=$(aws apigateway create-resource \
  --rest-api-id $API_ID \
  --parent-id $ROOT_ID \
  --path-part mcp \
  --query 'id' --output text)

echo "MCP_ID: $MCP_ID"

# STEP 3: Create POST method
aws apigateway put-method \
  --rest-api-id $API_ID \
  --resource-id $MCP_ID \
  --http-method POST \
  --authorization-type NONE

# STEP 4: Integrate with Lambda
aws apigateway put-integration \
  --rest-api-id $API_ID \
  --resource-id $MCP_ID \
  --http-method POST \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:418804479741:function:mcp_cust_score/invocations"

# STEP 5: Grant API Gateway permission to invoke Lambda
aws lambda add-permission \
  --function-name mcp_cust_score \
  --statement-id "apigw-$(date +%s)" \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:418804479741:$API_ID/*/POST/mcp"

# STEP 6: Deploy
aws apigateway create-deployment \
  --rest-api-id $API_ID \
  --stage-name prod

# STEP 7: Test
ENDPOINT="https://$API_ID.execute-api.us-east-1.amazonaws.com/prod/mcp"
echo ""
echo "✅ Endpoint: $ENDPOINT"
echo ""

echo "--- Test 1: Initialize ---"
curl -s -X POST $ENDPOINT \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}' | python3 -m json.tool

echo ""
echo "--- Test 2: List Tools ---"
curl -s -X POST $ENDPOINT \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | python3 -m json.tool

The create-deployment command returns a deployment id and timestamp. Construct the endpoint URL yourself using your API ID and Region in the following format: https://<API_ID>.execute-api.<REGION>.amazonaws.com/prod/mcp. Note this URL. You use it when registering the connector in Amazon Quick.

Production consideration: This walk-through exposes Lambda directly via Amazon API Gateway for simplicity. For production workloads, consider using Amazon Bedrock AgentCore Gateway instead, which provides a managed MCP endpoint with built-in authentication, rate limiting, and CloudWatch logging without requiring you to configure API Gateway manually.

To register the MCP Action connector in Amazon Quick

  1. From the Amazon Quick menu, select More, then select Connectors.
  2. Select Create for your team.

Connectors page with the Create for your team option

  1. Select Model Context Protocol.

Connector type selection showing Model Context Protocol

  1. In the screen that follows, Select No, create new.

New connector prompt with No, create new selected

  1. Enter the following values and choose Next:
  • Name: mcp-customer-score.
  • Description: Find score of customers based on aggregated KPIs.
  • MCP server endpoint: paste the endpoint URL https://<API_ID>.execute-api.<REGION>.amazonaws.com/prod/mcp.
  • Connection type: Public network.

MCP connector form with name, description, endpoint, and public network connection type

  1. Choose Next.
  2. On the Authenticate step, select Service authentication, then open the Auth configuration list and choose None. This endpoint is intentionally open for demonstration purposes. In a production environment, you would typically add an authentication layer, most commonly two-legged OAuth (2LO), to secure the endpoint. Because the scoring endpoint you deployed uses –authorization-type NONE, it accepts unauthenticated requests, so no client ID or secret is required. (Use an OAuth option only if your MCP server is protected by OAuth.)

Authenticate step with Service authentication and Auth configuration set to None

  1. Choose Create and continue.
  2. Choose Publish.
  3. Wait a few minutes, then choose the new connector in the Available section. Confirm the status shows Ready.

Available connectors list showing the mcp-customer-score connector with Ready status

To create the Quick S3 Action connector

The Amazon Quick S3 Action Connector is used by Quick Automate to download contact center raw data CSV from S3 and upload generated PDF bonus letters back to S3.

  1. Click on ‘Amazon Quick’ on the top left of page and then Click your profile icon (top-right). Then click on Manage account.
  2. From the admin menu, select AWS Actions

  3. In the page that follows, click on ‘New action
  4. In the page that follows, select Amazon S3 Under Storage, then Click Next.
  5. Select the S3 tools to enable. Then Click Next.
  6. In the page that follows enter Name as ‘q-automate-s3-connector’, Description as ‘q-automate-s3-connector’. For Role ARN, Provide the ARN of an IAM role that has permissions for the selected actions.The IAM role should:·       Have a policy granting the required S3 permissions (e.g., AmazonS3FullAccess or a scoped-down policy)·       Have QuickSight selected as the trusted service (use case) during role creationTo create the role: Create a role for an AWS service — Select QuickSight as the service use case, then attach your S3 permissions policy.Then click on ‘Next’.
  7. In the page that follows share this action with yourself by entering your Quick username (or the email used by your Quick username).
  8. Once the user is selected, it would appear in the panel. Then click on ‘Share
  9. The user will appear under the section ‘Shared with’. Then click on ‘Add’.  The connector will now be available for use in Quick Automate

To create a custom Chat Agent for contact center analysis

Now we will create a custom Chat Agent that connects to your ContactSpace . For this agent, the system prompt we will use is the critical component because it tells the agent how to interpret the data, what questions to ask of the transcripts, and when to invoke the scoring tool versus answering from the dataset directly.

  1. From the Amazon Quick menu, select Chat agents, then select +Blank, followed by Skip to create a custom chat agent from scratch.
  2. For Agent name, enter Contact Center Strategy Analyst.
  3. Under Knowledge Sources, link the ContactSpace.
  4. For the system prompt, enter the following:
You are a Contact Center Strategy Analyst. You have access to:
1. contact_center_data.csv - structured CSAT, call, and team data
2. Call_transcripts.docx - unstructured call transcript documents
When asked about at-risk customers:
- Query the structured dataset for low CSAT scores
- Cross-reference with transcripts to identify sentiment and root causes
- Format results as tables when showing multiple customers
  1. Choose Launch chat agent.

Chat agent configuration with the Launch chat agent option

To analyze at-risk customers with the Chat Agent

The Chat Agent accesses both the structured dataset and unstructured transcripts in your Space. Use it to identify at-risk customers and understand the reasons behind their low satisfaction scores.

  1. From the Amazon Quick menu, select New chat then select the drop down next to My Assistant to find and choose the Contact Center Strategy Analyst agent from the list.
  2. Enter the following prompt:
Show me customers that rated low CSAT (≤2) in Jan 2026 using contact center data

The agent returns a table of customer IDs, call dates, CSAT scores, and related metrics. This identifies who is at risk but not why.

  1. Enter a follow-up prompt:
Analyze sentiment for the above customers using transcripts

The agent reads the call transcripts and summarizes its findings. For example, it might identify that a specific customer expressed repeated frustration about billing errors across three calls. Another customer may have had a single intense interaction about a service outage. This distinction matters. Repeated small frustrations require a different retention approach than one major incident.

Chat agent returning a JSON array of at-risk customers

To convert the analysis into a Quick Flow

Beyond the one-time analysis, Quick Flows lets you turn a Chat conversation into a reusable automation with a single step, so the same analysis can be re-run on demand or on a schedule. The following steps demonstrate how to convert the conversation you just had into a Flow.

Note: Steps to create the Flow shown below provide a best-effort result based on the configured steps and prompt instructions. Results may require manual review and corrections before use. Always validate the output against your specific requirements.

  1. In the Chat interface, choose the + on the toolbar, then select Flow.
  2. Select Generate new flow, which suggests a prompt to use to build the Flow. Replace this prompt with “Generate a flow that queries the data in the “ContactSpace” space to identify customers with CSAT scores ≤ 2 for that time period, summarizes key at-risk signals (channel, first call resolution, resolution status, top call reasons, and repeat offenders), and then performs sentiment analysis on call transcripts for the identified repeat low-CSAT customers, outputting a detailed sentiment report including overall sentiment, key customer quotes, closing tone, churn risk reasoning, and common themes across customers using contact center data available within Quick” then select Generate.
  3. Review the flow then choose Publish.
  4. Select Run mode, if required enter any inputs then Start the flow.
  5. Download the output of the sentiment analysis section of the flow as a word document. Upload it to NegativeSentiment.docx under the input folder of the S3 bucket.

The Flow is now a reusable automation. Share it with your team or schedule it to run weekly to regenerate the analysis whenever you need it.

Quick Flow generated from the chat conversation

To create the Automate workflow

Connectors required: these connectors should be part of the Automation group that we create the Automate in.

  • Amazon S3 (two connections: one for source data, one for output uploads)
  • Custom Agent (with MCP scoring agent created in the MCP action connector step)

To create an Automation group

  1. On the Amazon Quick home page, choose More, then Automations.Amazon Quick home page with the Automations option under More
  2. In the page that follows, choose the ‘Groups’ tab.Automations page with the Groups tab selected
  3. Then choose Create Group, , then provide a name for it, for example “contact_center”.Groups tab with the Create Group option
    Create group dialog
  4. Choose the required actions, the MCP connector and the Amazon S3 connectors. Then choose Next.Group action selection with the MCP and Amazon S3 actions chosen
  5. On the following page, choose DoneGroup creation confirmation with the Done option

To create the Automation project

The above group will be used to create the Automation project which we will create in the subsequent section. Follow the instructions to create the Automation project.

Note: The automation steps shown below provides a best-effort output based on the prompt instructions. Results may require manual review and corrections before use. Always validate the output against your specific requirements.

  1. From the Amazon Quick menu, choose More, then Automations.
    Amazon Quick home page with the Automations option selected
  2. On the screen that follows, choose Create Project, then Create Project.Automations page with the Create Project option
  3. Enter a name for the automation, for example Contact Center Project choose the Automation group, then choose Create.New automation project dialog with a name and automation group selected
  4. On the following screen, choose Start buildingNew project screen with the Start building option
  5. Choose SkipAutomation builder prompt with the Skip optionThe following screen appears, where we will build an automated customer retention pipeline.Empty Automate canvas ready for building steps

To build the Automation steps

For the subsequent steps, follow the instructions shown below and complete the Automation project.

Step 1: Load call data from Amazon S3

Choose “Tell me what to build” and enter the following inside the chat assistant on the left that says ‘What do you want to automate’. This initial step loads call data from the raw contact center CSV in Amazon S3:

Log an info message: "Starting download of customer contact data from S3"
Download the file "contact_center_data.csv" from the S3 bucket amzn-s3-demo-bucket".
Read the downloaded file as a CSV with headers included and comma delimiter. Store the result as call_data.
Log an info message: "Successfully loaded customer contact data from S3 CSV file"
Have all the above under one step

Chat assistant showing the Step 1 instruction to download and read the contact center CSV

Choose Build Step 1.

Automate canvas after building Step 1

Choose the close (x) symbol in the top right.

Step configuration panel with the close control in the top right

The step will be created as shown below.

Automate canvas showing the created Step 1

Step 2: Filter high-risk customers

Choose the add (+) symbol below the step 1 box created above, then choose Process step as the first step towards creating Step 2 for filtering high-risk customers.

Add step menu with Process step selected

With the “Planning” option toggled to on,  enter and send the following prompt in the chat:

Log an info message: "Starting to filter call data for known high-risk customers with negative sentiment"
Download the file "input/NegativeSentiment.docx" from the S3 bucket "amzn-s3-demo-bucket".
Read the downloaded NegativeSentiment.docx file and extract the list of high-risk customer IDs from it. Store as at_risk_customer_ids.
Using an inline agent with both the python_repl coding tool AND the MCP tool "calculate_customer_scores", first have the agent use python_repl to aggregate the raw call_data rows by customer_id — computing total_calls (count of rows per customer), avg_csat_score (mean of csat_score), first_call_resolution_rate (percentage where first_call_resolution is true), avg_transfer_count (mean of transfer_count), avg_hold_time_seconds (mean of hold_time_seconds), complaint_ratio (ratio where call_reason equals "Complaint"), and resolved_rate (ratio where resolution_status equals "Resolved"). Then pass the aggregated per-customer metrics to the MCP tool "calculate_customer_scores" for loyalty scoring.
Store the MCP scoring result as aggregated_metrics. This will be a list of customers with calculated loyalty scores and tier assignments (Platinum/Gold/Silver/Bronze).
Filter aggregated_metrics to only include customers whose customer_id is in at_risk_customer_ids.
Store the filtered result as customer_aggregated_metrics.
Log an info message: "Filtered and scored {len(customer_aggregated_metrics)} at-risk customers using MCP scoring"
Have all the above under one step.

Chat window showing the Step 2 instruction to filter high-risk customers

Step 3: Aggregate customer metrics

Repeat the above for step 3 (Aggregate Customer Metrics) with following instruction:

Loop through each item in the aggregated metrics result table.
For each item, log an info message with this exact format:
"Customer: {item['customer_id']}, Total Calls: {item['total_calls']}, Avg CSAT: {item['avg_csat_score']}, FCR Rate: {item['first_call_resolution_rate']}%, Resolved Rate: {item['resolved_rate']}%, Avg Transfers: {item['avg_transfer_count']}, Avg Hold Time: {item['avg_hold_time_seconds']}s, Complaint Ratio: {item['complaint_ratio']}"
Have all the above under one step

These two instructions together will recreate the full Step 3 including its nested sub-step for logging. The main step handles all the calculation logic, and the sub-step handles the per-customer logging output.

Step 4: Identify top customers and generate letters

Repeat the above to create step 4 for identifying top customers then generating a letter for them. Use the following prompt:

Log an info message: "Starting customer bonus letter generation process"
Using the MCP tool "get_top_customers", pass customer_aggregated_metrics (from Step 2) as the scored_customers parameter. Set top_n to 2 and bonus_amount to 100.
Store the MCP result as top_customers_result. This will contain the top 2 ranked customers with bonus information.
Extract the top_customers list from top_customers_result and store as top_customers.
Create a helper function called generate_timestamp that returns the current datetime formatted as "%Y%m%d_%H%M%S". Call it and store as timestamp.
Create an empty list called bonus_letters.
For each customer in top_customers:
1. Get the customer_id from the customer object.
2. Create a PDF using create_pdf with the following HTML content:
- Style: body with font-family Arial, line-height 1.6, margin 40px, color #333
- A "header" div centered with h2 "Customer Retention Department" and italic paragraph "Valued Customer Appreciation Program"
- A "content" div containing:
- Bold greeting: "Dear Valued Customer (ID: {customer_id}),"
- Paragraph expressing appreciation for continued partnership
- Paragraph acknowledging past service issues and offering heartfelt apologies, mentioning their feedback has been invaluable
- A "highlight" div (background-color #f0f8ff, padding 15px, border-left 4px solid #007acc, margin 20px 0) containing bold text: "As a gesture of our commitment to your satisfaction and our appreciation for your loyalty, we are pleased to offer you a \$100 bonus credit."
- Paragraph about the bonus reflecting genuine desire to make things right and mutual success
- Paragraph about enhanced service protocols and dedicated customer success team
- Paragraph hoping for opportunity to demonstrate renewed commitment
- A "signature" div with margin-top 40px containing:
- "With sincere regards and appreciation,"
- A line break
- Bold "Customer Retention Team", then "Customer Success Department", then italic "Dedicated to Your Success"
3. Upload the PDF to S3 bucket "amzn-s3-demo-bucket" with Key = "output/" + customer_id + "_" + timestamp + ".pdf"
4. Append the PDF upload result to the bonus_letters list.
After the loop completes, log: "Successfully generated bonus letters for top 2 customers"
Have all the above under one step
 

To test and deploy the automation

Validate the automation end to end, then commit and deploy it so it can run on a schedule.

  1. In the canvas, choose Debug (or Test) to run the automation. It runs on its own, downloading the at-risk customers file from Amazon S3 through the Download file step. Review each step’s output in the logs panel.
  2. Confirm the scoring step returns the top two customers, the letters reference the correct customer issues, and the S3 upload completes successfully.
  3. When the run is correct, choose Commit to create a version.
  4. Choose Deploy, select the committed version, and confirm the required credentials and connections (your Amazon S3 connection and the mcp-customer-score connector).
  5. To run the automation on a schedule, on the Deployment page choose Create Trigger, then set the frequency (or a cron expression), start time, and time zone.

For more details refer to the following links.

https://docs.aws.amazon.com/quick/latest/userguide/testing-automations.html

https://docs.aws.amazon.com/quick/latest/userguide/deploying-automations.html

Results

After deploying this pipeline in a test environment with historical contact center data, the team measured improvements across four dimensions: response time, retention rates, offer acceptance, and time to deploy. Internal testing with sample data produced these results. Your implementation may vary.

  • Response time dropped from days of manual identification and drafting to minutes of automated execution.
  • Retention rates improved among flagged customers compared to the previous reactive approach. Faster outreach, while the experience is still fresh, drove this improvement.
  • Offer acceptance increased when letters referenced the customer’s specific issue rather than offering a generic discount.
  • Time to deploy took less than one day. Business users built the full workflow through natural language and point-and-click screens.

Troubleshooting

The following table lists common issues and their resolutions.

Symptom Cause Resolution
Chat Agent returns “No data found” Space indexing incomplete Verify ContactSpace status. Wait for the Ready indicator before running queries.
Sentiment analysis is generic Transcripts not indexed Verify call transcript documents are uploaded to the Space Documents section.
Automation scores no customers or the wrong ones At-risk JSON not found, or the agent did not receive it Confirm at_risk_customers.json exists at the Amazon S3 path the Download file step references, and that the scoring agent’s instructions reference the downloaded variable.
MCP Action returns timeout Endpoint not responding Increase the MCP Action timeout or reduce the dataset scope.
Letters are generic or missing customer details Letter agent did not receive the scored results Confirm the letter agent’s prompt references the scoring step’s output variable (inserted with @).

Cleaning up

To avoid ongoing costs when you no longer need these resources:

  1. Delete the Automate workflow.
  2. Delete the Flow.
  3. Remove input datasets and generated letters from your S3 bucket:
aws s3 rm s3://amzn-s3-demo-bucket/inputs/ --recursive
aws s3 rm s3://amzn-s3-demo-bucket/output/letters/ --recursive
  1. Deregister the custom MCP Action endpoint if it was created specifically for this workflow.
  2. Remove uploaded data from ContactSpace if no longer needed.

Conclusion

This walk-through demonstrated how to build a customer retention pipeline that goes from raw contact center data to targeted retention actions, without writing application code. The pipeline combines KPI monitoring and AI-powered transcript analysis through the Chat Agent. It adds repeatable automation through Quick Flows and multi-step processing through Quick Automate.

After the initial developer setup (Lambda function and API Gateway), business users built and operated the remaining components through natural language and point-and-click interfaces. The MCP Action connector extended the pipeline with custom scoring logic that reflects your organization’s specific retention criteria.

Apply this same pattern to adjacent use cases: client account health monitoring, compliance risk escalation, or proactive service recovery. Start by connecting your data to a Quick Space and asking questions through the Chat Agent. Once you have the right analysis, convert it to a Flow and orchestrate the workflow with Automate.

For more information, refer to the Amazon Quick documentation. To explore the MCP Action framework, refer to the MCP action framework.


About the authors

Vaidy Janardhanam

Vaidy Janardhanam

Vaidy is a Specialist Solutions Architect for Amazon Quick Suite. He specializes in empowering customers to transform their analytics landscape, unifying traditional business intelligence dashboards with cutting-edge agentic AI workflows that turn insights into automated action.

Pegah Ojaghi

Pegah Ojaghi

Pegah is a Generative AI Applied Architect at AWS with a PhD in Computer Science focused on large language models, generative AI, and reinforcement learning. Her expertise spans foundation model development, RLHF techniques, and novel optimization methods for LLMs. She is passionate about translating cutting-edge research into production systems across the healthcare, financial services, and insurance industries.

Oyin Oguntoye

Oyin Oguntoye

Oyin is an AWS Solutions Architect in the Amazon Quick team. She is passionate about problem solving particularly in the data, analytics and AI space. Oyin has spent a number of years at Amazon helping implement solutions both internally for Amazon, and also externally for AWS customers.