Boost Customer Support: AI Agents, LangGraph, and RAG for Email Automation

Boost Customer Support: AI Agents, LangGraph, and RAG for Email Automation

·

16 min read

In the age of instant gratification, customer support is no longer a department, it's the entire company.

Did you know that 86% of customers will churn after just one bad experience?

Traditional support teams are overwhelmed, struggling to manage endless email queues.

But the game is changing. Large Language Models (LLMs) and AI Agents are transforming customer service, enabling businesses to meet rising expectations with speed and precision. Studies show that AI can boost customer satisfaction by 15–25% and cut costs by 20–40%.

The era of manual email processing is over. AI automation is here to deliver the fast, high-quality support your customers demand.

In this technical guide, we’ll explore how I built an AI Customer Support Email Automation System using LangGraph, LangChain, and RAG to:

  • Monitor emails 24/7 and deliver rapid responses to customer inquiries.

  • Leverage AI agents to intelligently sort emails into categories and craft tailored responses.

  • Use Retrieval-Augmented Generation (RAG) to provide precise answers to product and service questions.

Let's dive in!

AI Email Automation with LangGraph and RAG

To better understand the AI Email Automation system I built, it's helpful to first introduce the core technologies that power it: LangChain, LangGraph, and Retrieval-Augmented Generation (RAG).

LangChain and LangGraph

LangChain is a framework for developing AI applications using different LLMs (like GPT-4o, Google Gemini, LLAMA3) and LangGraph is built on top of it, extending its capabilities by adding the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner.

Think of LangGraph as the workflow orchestrator. It excels at managing complex, multi-step processes by representing them as a graph—a network of interconnected nodes. This allows for flexible and dynamic interactions, enabling the system to adapt to various scenarios and user inputs.

What is RAG?

You’ve likely heard of Retrieval-Augmented Generation (RAG), but let me briefly explain its role. Most LLMs are trained periodically on large public datasets, meaning they often lack recent information or access to private data unavailable during training.

RAG addresses this limitation by connecting LLMs to external data sources. It retrieves relevant information from a knowledge base (such as company documents, websites, or FAQs) and uses that data to generate accurate and contextually appropriate responses. In essence, it’s like equipping the AI with a reference library to consult before answering.

Alright, now that we've covered the basics of LangChain, LangGraph, and RAG, let's see what this system can actually do!

High-Level System Overview

The automation system operates by continuously monitoring the agency's inbox, categorizing emails, generating tailored responses, and ensuring quality before sending them. Below is a visual representation of the process:

A. Email Monitoring and Categorization

Using the Gmail API, the system actively monitors the agency's inbox. New emails are immediately identified and categorized by the AI classification agent into four key classes:

  • Customer Complaint: Emails detailing dissatisfaction or issues.

  • Product Inquiry: Emails requesting information about offerings.

  • Customer Feedback: Emails providing general opinions, suggestions, or praise.

  • Unrelated: Emails irrelevant to the agency’s operations, which are ignored.

B. Dynamic Response Generation

The response process adapts based on the email's category:

  • Complaints and Feedback: These emails are routed directly to an AI writer agent, which generates personalized responses acknowledging the customer’s concerns or expressing gratitude for their feedback. RAG (Retrieval-Augmented Generation) is not required for these cases.

  • Product Inquiries: For these emails, we use RAG to retrieve accurate and up-to-date information from the agency’s knowledge base (e.g., product documentation, FAQs). This information is then used by the AI writer agent to craft a comprehensive and precise response.

C. AI Quality Assurance

Every email undergoes quality checks before being sent. An AI proofreader agent ensures:

  • Formatting: The email is visually appealing and easy to read.

  • Relevance: The response directly addresses the customer’s queries or concerns.

  • Tone: The tone aligns with the agency’s professional and brand-specific standards.

D. Automated Email Dispatch

Once an email passes the quality assurance stage, it is promptly sent to the customer, ensuring swift and efficient communication.

How It Works: Step-by-Step Breakdown

Setting Up the Knowledge Base for RAG

Since our system relies on Retrieval-Augmented Generation (RAG), we need a well-prepared knowledge base containing information about the agency. This will help us provide accurate and relevant answers to customer inquiries about products and services.

For this project, I've already created a sample agency profile using ChatGPT. To use it, we'll follow the standard steps for setting up a RAG knowledge base:

  1. Load the Documents: We'll start by loading the agency documents.

  2. Chunk the Documents: We'll then split the document into smaller, manageable chunks. This is important because large segments can be difficult for LLMs to process effectively.

  3. Generate Embeddings: We'll use an embedding model to convert each chunk into a vector representation.

  4. Create a Vector Database: Finally, we'll create a vector database to store these embeddings.

To keep things simple for this project, I've used Chroma DB, a local vector database, along with Google's embedding model. For production environments, you might consider more scalable solutions like Pinecone or Qdrant.

Here's the code that accomplishes this setup:

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# Loading Docs
loader = TextLoader("./data/agency.txt")
docs = loader.load()

# Split document into chuncks
doc_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)
doc_chunks = doc_splitter.split_documents(docs)

# Creating vector embeddings
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001") # Updated model name
vectorstore = Chroma.from_documents(doc_chunks, embeddings, persist_directory="db")

# Semantic vector search retriever
vectorstore_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

With the knowledge base set up, we can now visualize the core of the system: the automation graph built with LangGraph. Here's how it looks:

# load inbox emails
workflow.set_entry_point("load_emails")

# check if there are new emails to process
workflow.add_edge("load_emails", "is_email_inbox_empty")
workflow.add_conditional_edges(
    "is_email_inbox_empty",
    nodes.check_new_emails,
    {
        "process": "categorize_email",
        "empty": END
    }
)

# route email based on category
workflow.add_conditional_edges(
    "categorize_email",
    nodes.route_email_based_on_category,
    {
        "product related": "construct_rag_queries",
        "not product related": "email_writer", # Feedback or Complaint
        "unrelated": "skip_unrelated_email"
    }
)

# pass constructed queries to RAG chain to get information
workflow.add_edge("construct_rag_queries", "retrieve_from_rag")
# give information to writer agent to write the email
workflow.add_edge("retrieve_from_rag", "email_writer")
# proofread the generated email
workflow.add_edge("email_writer", "email_proofreader")
# check if email is sendable or not, if not rewrite the email
workflow.add_conditional_edges(
    "email_proofreader",
    nodes.must_rewrite,
    {
        "send": "send_email",
        "rewrite": "email_writer"
    }
)

# check if there are still emails to be processed
workflow.add_edge("send_email", "is_email_inbox_empty")
workflow.add_edge("skip_unrelated_email", "is_email_inbox_empty" )

Let's explore each part of this automation separately!

1. Email Monitoring

The process begins with continuous monitoring of the agency's Gmail inbox. This is achieved using the Gmail API, which allows the system to regularly check for new emails.

I built a special class GmailToolsClass to handle everything related to Gmail. You can check out the full code in my Github. For now, let's focus on how it fetches new emails:

class GmailToolsClass:
    def __init__(self):
        self.service = self._get_gmail_service()

    def fetch_unanswered_emails(self, max_results=50):
        try:
            # 1. Get recent emails
            recent_emails = self.fetch_recent_emails(max_results)
            if not recent_emails: return []

            # 2. Get draft replies to filter out already answered threads
            drafts = self.fetch_draft_replies()
            threads_with_drafts = {draft['threadId'] for draft in drafts}

            # 3. Process each new email
            seen_threads = set()
            unanswered_emails = []
            for email in recent_emails:
                thread_id = email['threadId']
                # Skip if thread was seen before or has a draft reply
                if thread_id not in seen_threads and thread_id not in threads_with_drafts:
                    seen_threads.add(thread_id)
                    email_info = self._get_email_info(email['id'])
                    # Skip if email is sent from the agency (not received)
                    if self._should_skip_email(email_info):
                        continue
                    unanswered_emails.append(email_info)
            return unanswered_emails

        except Exception as e:
            print(f"An error occurred: {e}")
            return []

    def fetch_recent_emails(self, max_results=50):
        """Fetches emails from the last 8 hours."""
        try:
            # Query emails from the last 8 hours
            now = datetime.now()
            delay = now - timedelta(hours=8)
            after_timestamp = int(delay.timestamp())
            before_timestamp = int(now.timestamp())
            query = f"after:{after_timestamp} before:{before_timestamp}"

            results = self.service.users().messages().list(
                userId="me", q=query, maxResults=max_results
            ).execute()
            messages = results.get("messages", [])
            return messages

        except Exception as error:
            print(f"An error occurred while fetching emails: {error}")
            return []

    # ... other methods ...

    def _get_email_info(self, msg_id):
        message = self.service.users().messages().get(
            userId="me", id=msg_id, format="full"
        ).execute()

        payload = message.get('payload', {})
        headers = {header["name"].lower(): header["value"] for header in payload.get("headers", [])}

        return {
            "id": msg_id,
            "threadId": message.get("threadId"),
            "messageId": headers.get("message-id"),
            "references": headers.get("references", ""),
            "sender": headers.get("from", "Unknown"),
            "subject": headers.get("subject", "No Subject"),
            "body": self._get_email_body(payload),
        }

Ok what’s going on inside fetch_unanswered_emails ?

  • We use fetch_recent_emails to retrieve emails from the past 8 hours.

  • Draft replies are checked to avoid loading already-answered emails.

  • We loop over all new emails to gather key details (sender, subject, body), making sure to skip any emails sent by the agency itself.

2. Email Categorization

Once new emails are detected, an AI agent categorize them into four predefined categories: "Customer Complaint", "Product Inquiry", "Customer Feedback" or "Unrelated".

Below are the instructions used for the AI categorization agent:

CATEGORIZE_EMAIL_PROMPT = """
# **Role:**

You are a highly skilled customer support specialist working for a SaaS company specializing in AI agent design. Your expertise lies in understanding customer intent and meticulously categorizing emails to ensure they are handled efficiently.

# **Instructions:**

1. Review the provided email content thoroughly.
2. Use the following rules to assign the correct category:
   - **product_enquiry**: When the email seeks information about a product feature, benefit, service, or pricing.
   - **customer_complaint**: When the email communicates dissatisfaction or a complaint.
   - **customer_feedback**: When the email provides feedback or suggestions regarding a product or service.
   - **unrelated**: When the email content does not match any of the above categories.

# **EMAIL CONTENT:**
{email}

# **Notes:**

* Base your categorization strictly on the email content provided; avoid making assumptions or overgeneralizing.
"""

Then we call this agent in the categorize_email node of our graph:

def categorize_email(self, state: GraphState) -> GraphState:
    """Categorizes the current email using the categorize_email agent."""
    # Get the last email
    current_email = state["emails"][-1]
    result = self.agents.categorize_email.invoke({"email": current_email.body})
    print(f"Email category: {result.category.value}")

    return {
        "email_category": result.category.value,
        "current_email": current_email
    }

3. Response Generation

The system generates responses differently depending on the email's category:

a. Complaints and Feedback:

For emails categorized as complaints or feedback, the AI writer agent is directly tasked with crafting a personalized response. The writer agent is instructed to be empathetic, professional, and to ensure its responses align with the agency's brand voice:

EMAIL_WRITER_PROMPT = """
# **Role:**  

You are a professional email writer working as part of the customer support team at a SaaS company specializing in AI agent development. Your role is to draft thoughtful and friendly emails that effectively address customer queries based on the given category and relevant information.  

# **Tasks:**  

1. Use the provided email category, subject, content, and additional information to craft a professional and helpful response.  
2. Ensure the tone matches the email category, showing empathy, professionalism, and clarity.  
3. Write the email in a structured, polite, and engaging manner that addresses the customer’s needs.  

...

Write the email in the following format:  
   Dear [Customer Name],  

   [Email body responding to the query, based on the category and information provided.]  

   Best regards,  
   The Agentia Team    
   - Replace `[Customer Name]` with “Customer” if no name is provided.  
   - Ensure the email is friendly, concise, and matches the tone of the category.  

3. If a feedback is provided, use it to improve the email while ensuring it still aligns with the predefined guidelines.  

# **Notes:**  

* Return only the final email without any additional explanation or preamble.  
* Always maintain a professional and empathetic tone that aligns with the context of the email.  
* If the information provided is insufficient, politely request additional details from the customer.  
* Make sure to follow any feedback provided when crafting the email.  
"""

Here's how the write_draft_email function uses this prompt to create a draft:

def write_draft_email(self, state: GraphState) -> GraphState:
    """Writes a draft email based on the current email and retrieved information."""
    # Get messages history for current email
    writer_messages = state.get('writer_messages', [])

    # Write email
    draft_result = self.agents.email_writer.invoke({
        "email_category": state["email_category"],
        "email_content": state["current_email"].body,
        "retrieved_documents": state["retrieved_documents"], # Empty for feedback or complaint
        "history": writer_messages
    })
    email = draft_result.email
    trials = state.get('trials', 0) + 1

    # Append writer's draft to the message list
    writer_messages.append(f"**Draft {trials}:**\n{email}")

    return {
        "generated_email": email,
        "trials": trials,
        "writer_messages": writer_messages
    }

We keep track of the writer agent's message history (writer_messages) and the number of drafting attempts (trials) for reasons that will become clear in the next section.

b. Product Inquiries:

For emails categorized as product inquiries, the system will use RAG to provide accurate and detailed answers.

1. Query Construction: The first step is to transform the customer's email into specific queries that can be used to search the internal knowledge base. This is done using the below prompt:

GENERATE_RAG_QUERIES_PROMPT = """
# **Role:**

You are an expert at analyzing customer emails to extract their intent and construct the most relevant queries for internal knowledge sources.

# **Context:**

You will be given the text of an email from a customer. This email represents their specific query or concern. Your goal is to interpret their request and generate precise questions that capture the essence of their inquiry.

# **Instructions:**

1. Carefully read and analyze the email content provided.
2. Identify the main intent or problem expressed in the email.
3. Construct up to three concise, relevant questions that best represent the customer’s intent or information needs.
4. Include only relevant questions. Do not exceed three questions.
5. If a single question suffices, provide only that.

# **EMAIL CONTENT:**
{email}

# **Notes:**

* Focus exclusively on the email content to generate the questions; do not include unrelated or speculative information.
* Ensure the questions are specific and actionable for retrieving the most relevant answer.
* Use clear and professional language in your queries.
"""

The node construct_rag_queries calls this AI agent to generate queries:

def construct_rag_queries(self, state: GraphState) -> GraphState:
    """Constructs RAG queries based on the email content."""
    email_content = state["current_email"].body
    query_result = self.agents.design_rag_queries.invoke({"email": email_content})

    return {"rag_queries": query_result.queries}

2. Information Retrieval and Response Generation:

Next, RAG uses these queries to search through the knowledge base. The retrieved information serves as context for a specialized QA AI agent, which synthesizes accurate responses to each query:

GENERATE_RAG_ANSWER_PROMPT = """
# **Role:**

You are a highly knowledgeable and helpful assistant specializing in question-answering tasks.

# **Context:**

You will be provided with pieces of retrieved context relevant to the user's question. This context is your sole source of information for answering.

# **Instructions:**

1. Carefully read the question and the provided context.
2. Analyze the context to identify relevant information that directly addresses the question.
3. Formulate a clear and precise response based only on the context. Do not infer or assume information that is not explicitly stated.
4. If the context does not contain sufficient information to answer the question, respond with: "I don't know."
5. Use simple, professional language that is easy for users to understand.

# **Question:**
{question}

# **Context:**
{context}

# **Notes:**

* Stay within the boundaries of the provided context; avoid introducing external information.
* If multiple pieces of context are relevant, synthesize them into a cohesive and accurate response.
* Prioritize user clarity and ensure your answers directly address the question without unnecessary elaboration.
"""

The retrieve_from_rag node retrieves information for each query and assembles the answers:

def retrieve_from_rag(self, state: GraphState) -> GraphState:
    """Retrieves information from internal knowledge based on RAG questions."""
    final_answer = ""
    for query in state["rag_queries"]:
        rag_result = self.agents.generate_rag_answer.invoke(query)
        final_answer += query + "\n" + rag_result + "\n\n"

    return {"retrieved_documents": final_answer}

All queries with their respective answers are saved in retrieved_documents which is then used by the AI writer agent as context to craft the final email response, ensuring it is both comprehensive and accurate.

4. Quality Assurance: Making Sure Emails are Perfect

Before any email is sent, it undergoes a thorough quality check by an AI proofreader agent. The agent reviews each email for proper formatting, relevance to customer inquiries, and a professional, brand-consistent tone.

The AI proofreader agent is guided by the below prompt that outlines its responsibilities:

EMAIL_PROOFREADER_PROMPT = """
# **Role:**

You are an expert email proofreader working for the customer support team at a SaaS company specializing in AI agent development. Your role is to analyze and assess replies generated by the writer agent to ensure they accurately address the customer's inquiry, adhere to the company's tone and writing standards, and meet professional quality expectations.

# **Context:**

You are provided with the **initial email** content written by the customer and the **generated email** crafted by the our writer agent.

# **Instructions:**

1. Analyze the generated email for:
   - **Accuracy**: Does it appropriately address the customer’s inquiry based on the initial email and information provided?
   - **Tone and Style**: Does it align with the company’s tone, standards, and writing style?
   - **Quality**: Is it clear, concise, and professional?
2. Determine if the email is:
   - **Sendable**: The email meets all criteria and is ready to be sent.
   - **Not Sendable**: The email contains significant issues requiring a rewrite.
3. Only judge the email as "not sendable" (`send: false`) if lacks information or inversely contains irrelevant ones that would negatively impact customer satisfaction or professionalism.
4. Provide actionable and clear feedback for the writer agent if the email is deemed "not sendable."

---

# **INITIAL EMAIL:**
{initial_email}

# **GENERATED REPLY:**
{generated_email}

---

# **Notes:**

* Be objective and fair in your assessment. Only reject the email if necessary.
* Ensure feedback is clear, concise, and actionable.
"""

The verify_generated_email node calls the proofreader agent to check the email:

def verify_generated_email(self, state: GraphState) -> GraphState:
    """Verifies the generated email using the proofreader agent."""
    print(Fore.YELLOW + "Verifying generated email...\n" + Style.RESET_ALL)
    review = self.agents.email_proofreader.invoke({
        "initial_email": state["current_email"].body,
        "generated_email": state["generated_email"],
    })

    # Store feedback if email is not good
    if not review.send:
        writer_messages = state.get('writer_messages', [])
        writer_messages.append(f"**Proofreader Feedback:**\n{review.feedback}")

    return {
        "sendable": review.send,
        "writer_messages": writer_messages
    }

Here's how it works: The function provides both the customer's original email and the drafted response to the AI proofreader agent, which either approves the email (sendable: True) or rejects it (sendable: False) with feedback. If it's rejected, the feedback is saved to writer_messages list so the AI writer agent can use it to improve the response.

To prevent the system from getting stuck in an infinite loop (which can happen if the necessary information to answer the email is not present in the knowledge base), we only give it three tries (trials = 3) to get it right. If it still can't produce a good email after three attempts, we flag it for a human to review. This makes sure that every email sent out is high quality.

5. Email Dispatch

Finally, once the email has passed all quality checks, it's ready to be sent. The system uses the Gmail API to automatically send the finalized reply to the customer.

There are two options available, the first is to create a draft email for human review:

def create_draft_response(self, state: GraphState) -> GraphState:
    """Creates a draft response in Gmail."""
    print(Fore.YELLOW + "Creating draft email...\n" + Style.RESET_ALL)
    self.gmail_tools.create_draft_reply(state["current_email"], state["generated_email"])

    return {"retrieved_documents": "", "trials": 0}

The second is to send the email directly:

def send_email_response(self, state: GraphState) -> GraphState:
    """Sends the email response directly using Gmail."""
    print(Fore.YELLOW + "Sending email...\n" + Style.RESET_ALL)
    self.gmail_tools.send_reply(state["current_email"], state["generated_email"])

    return {"retrieved_documents": "", "trials": 0}

What happens next?

After sending the email or creating a draft, the system loops back to the beginning to check for any new emails. This keeps the process going, ensuring all customer emails are handled quickly and efficiently, until the inbox is empty.

Benefits and Impact

  • Improved Customer Experience: Provides quick, tailored responses that make customers feel valued.

  • Efficiency Boost: Dramatically reduces the time spent on email management.

  • Team Productivity: Frees up support teams to focus on complex tasks and strategic initiatives.

  • Accuracy Guaranteed: RAG ensures every response is backed by reliable information.

Real-World Applications

This email automation system offers versatile solutions for various industries:

  • Marketing Agencies: Handles client emails, automates reporting, and answers questions quickly, letting teams focus on creative work.

  • E-commerce: Answers product questions, helps with order updates, and collects feedback to improve customer experiences.

  • Tech Companies: Responds to common technical issues, provides customer support, and organizes feedback from beta testers.

  • Education: Helps with student inquiries, guides them through enrollment, and supports administrative tasks.

The system is flexible and works well for any business that wants to handle emails more effectively and improve communication with customers.

Conclusion

This AI email automation system, built on LangChain and LangGraph, and utilizing RAG, offers a powerful solution for businesses seeking to transform their customer support. By automating email management, it delivers faster response times, improves customer satisfaction, and boosts operational efficiency.

🎯 Ready to revolutionize your customer interactions? Check out the complete project on my GitHub and discover how to tailor this system to your own business!