Custom tools
Learn how to create custom tools to extend your agent’s capabilities beyond message handling.
Letta’s stateful AI agents can enrich n8n workflows. This guide demonstrates how to create a routing system for customer support tickets by integrating n8n automation with a Letta agent. The agent remembers customer history, learns routing patterns, and provides intelligent recommendations.
Both n8n and Letta can be self-hosted if you need to run them on your own infrastructure (we’ll cover self-hosting in a future guide).
You’ll create the following customer support workflow, and in doing so, learn to add Letta agents to n8n automations:
graph LR
A[Google Sheets<br/>New row] --> B[Letta agent<br/>Intelligence layer]
B --> C[Slack<br/>Notification]
The Letta agent sits between your trigger and action, analyzing each ticket with full context from previous interactions. You’ll build this visually in the n8n canvas by connecting nodes — no complex configuration required.
To follow along, you need:
You need an API key for Letta.
Create a Letta account
If you don’t have one, sign up for a free account at letta.com.
Navigate to API keys
Once logged in, click on API keys in the sidebar.

Create and copy your key
Click + Create API key, give it a descriptive name (such as n8n Integration), and click Confirm. Copy the key and save it somewhere safe.
Once you have these credentials, create a .env file in your project directory and add your API key as an environment variable:
LETTA_API_KEY=your_letta_api_key_hereIf you don’t have an account, sign up at n8n.io to get a 14-day free trial of n8n Cloud.
Create a new Google Sheet called Support Tickets with the following columns:
Ticket ID (Column A)Customer Name (Column B)Customer Email (Column C)Subject (Column D)Description (Column E)Priority (Column F)Make sure you have access to a Slack workspace where you can receive notifications (you’ll connect the workspace to n8n in Step 2).
First, configure a Letta agent with memory blocks for customer history and routing knowledge.
Create a requirements.txt file that contains the following dependencies:
letta-clientpython-dotenvRun the following code to install the dependencies:
pip install -r requirements.txtCreate a package.json file with the following contents:
{ "name": "letta-n8n-integration", "version": "1.0.0", "description": "TypeScript code for integrating Letta agents with n8n workflows", "type": "module", "scripts": { "create-agent": "tsx create-agent.ts", "test-ticket": "tsx test-ticket.ts" }, "dependencies": { "@letta-ai/letta-client": "latest", "dotenv": "^16.3.1" }, "devDependencies": { "@types/node": "^20.10.0", "tsx": "^4.7.0", "typescript": "^5.3.0" }}Create a tsconfig.json file with the following contents:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "strict": true, "outDir": "./dist", "rootDir": "." }, "include": [ "*.ts" ], "exclude": [ "node_modules", "dist" ]}Install the dependencies:
npm installCreate a script to initialize your Letta agent with three memory blocks:
persona block to define the agent’s role and behaviorrouting_knowledge block to track ticket-routing patternscustomer_history block to maintain the memory of customer interactionsCreate a file named create-agent.py and add the following code to it:
import osfrom letta_client import Lettafrom dotenv import load_dotenv
# Load environment variablesload_dotenv()
def create_support_agent(): """Create and configure a support ticket routing agent."""
# Initialize the Letta client api_key = os.getenv("LETTA_API_KEY") if not api_key: raise ValueError("LETTA_API_KEY environment variable not set. Please add it to your .env file.")
client = Letta(api_key=api_key) print("✓ Connected to Letta Cloud")
# Create the agent with memory blocks agent = client.agents.create( name="Support Ticket Router", description="Intelligent support ticket routing assistant with customer memory and learning capabilities", memory_blocks=[ { "label": "persona", "value": """I am an intelligent support ticket routing assistant. My role is to:- Analyze incoming support tickets with full context- Remember customer history across all interactions- Learn which routing decisions lead to fastest resolutions- Provide detailed routing recommendations with reasoning- Continuously improve my routing accuracy over time
I always provide structured responses with routing recommendations, priority levels, and context.""" }, { "label": "routing_knowledge", "value": """I track patterns about ticket routing and resolutions:- Which teams handle which types of issues most effectively- Average resolution times for different issue categories- Patterns in urgent vs routine tickets- Success rates of different routing decisions
I learn from each ticket to improve future routing decisions.""" }, { "label": "customer_history", "value": """I maintain memory of customer interactions:- Previous tickets and their resolutions- Communication preferences (technical level, urgency patterns)- Account-specific issues and patterns- Customer satisfaction indicators
I use this history to provide personalized, context-aware routing.""" } ] )
print(f"Agent created: {agent.id}") print(f"\nAdd this to your .env file:") print(f"LETTA_AGENT_ID={agent.id}")
return agent
if __name__ == "__main__": try: agent = create_support_agent() except Exception as e: print(f"\nError creating agent: {e}") raiseCreate a file named create-agent.ts and add the following code to it:
import Letta from '@letta-ai/letta-client';import * as dotenv from 'dotenv';
// Load environment variablesdotenv.config();
async function createSupportAgent() { /** * Create and configure a support ticket routing agent. */
// Initialize the Letta client const apiKey = process.env.LETTA_API_KEY; if (!apiKey) { throw new Error("LETTA_API_KEY environment variable not set. Please add it to your .env file."); }
const client = new Letta({ apiKey: apiKey }); console.log("✓ Connected to Letta Cloud");
// Create the agent with memory blocks const agent = await client.agents.create({ name: "Support Ticket Router", description: "Intelligent support ticket routing assistant with customer memory and learning capabilities", memory_blocks: [ { label: "persona", value: `I am an intelligent support ticket routing assistant. My role is to:- Analyze incoming support tickets with full context- Remember customer history across all interactions- Learn which routing decisions lead to fastest resolutions- Provide detailed routing recommendations with reasoning- Continuously improve my routing accuracy over time
I always provide structured responses with routing recommendations, priority levels, and context.` }, { label: "routing_knowledge", value: `I track patterns about ticket routing and resolutions:- Which teams handle which types of issues most effectively- Average resolution times for different issue categories- Patterns in urgent vs routine tickets- Success rates of different routing decisions
I learn from each ticket to improve future routing decisions.` }, { label: "customer_history", value: `I maintain memory of customer interactions:- Previous tickets and their resolutions- Communication preferences (technical level, urgency patterns)- Account-specific issues and patterns- Customer satisfaction indicators
I use this history to provide personalized, context-aware routing.` } ] });
console.log(`Agent created: ${agent.id}`); console.log(`\nAdd this to your .env file:`); console.log(`LETTA_AGENT_ID=${agent.id}`);
return agent;}
// Run the scriptcreateSupportAgent().catch((error) => { console.error(`\nError creating agent: ${error.message}`); process.exit(1);});python create-agent.pynpx tsx create-agent.tsIt should return an output similar to the following:
Connected to Letta CloudAgent created: agent-abc123def456
Add this to your .env file:LETTA_AGENT_ID=agent-abc123def456Copy your agent ID from the output and add it to your .env file as follows:
LETTA_API_KEY=your_letta_api_key_hereLETTA_AGENT_ID=agent-abc123def456Before connecting to n8n, test whether your agent analyzes tickets correctly.
This script sends sample support tickets to your agent and displays the routing recommendations.
Create a file named test-ticket.py and add the following code to it:
import osimport jsonfrom letta_client import Lettafrom dotenv import load_dotenv
# Load environment variablesload_dotenv()
# Sample test ticketsSAMPLE_TICKETS = [ { "customer_name": "John Doe", "subject": "Cannot log in to dashboard", "description": "I'm getting a 500 error when trying to log in. This is the third time this month.", "priority": "High" }, { "customer_name": "Sarah Smith", "subject": "Question about billing", "description": "I was charged twice for my subscription this month. Can someone look into this?", "priority": "Medium" }, { "customer_name": "Mike Johnson", "subject": "Feature request", "description": "Would love to see dark mode added to the app. Many users have been asking for this.", "priority": "Low" }]
def format_ticket_message(ticket): """Format a ticket into a message for the Letta agent.""" return f"""New support ticket received:
Customer: {ticket['customer_name']} ({ticket['customer_email']})Subject: {ticket['subject']}Description: {ticket['description']}Priority: {ticket['priority']}
Please analyze this ticket and provide:1. Recommended team to route to2. Adjusted priority level (if needed)3. Your reasoning based on customer history and patterns4. A suggested response message5. Estimated resolution time"""
def send_ticket_to_agent(client, agent_id, ticket): """Send a ticket to the Letta agent and get routing recommendation."""
# Send message to agent message_content = format_ticket_message(ticket)
response = client.agents.messages.create( agent_id=agent_id, messages=[ { "role": "user", "content": message_content } ] )
# Extract and display the agent's response for message in response.messages: if message.message_type == 'assistant_message': print(message.content) print()
return response
def main(): """Main test function."""
# Get credentials from environment api_key = os.getenv("LETTA_API_KEY") agent_id = os.getenv("LETTA_AGENT_ID")
if not api_key: raise ValueError("LETTA_API_KEY not set. Add it to your .env file.")
if not agent_id: raise ValueError("LETTA_AGENT_ID not set. Run create-agent.py first and add the ID to your .env file.")
# Initialize client client = Letta(api_key=api_key) print("Connected to Letta Cloud") print(f"Using agent: {agent_id}\n")
# Test with sample tickets for i, ticket in enumerate(SAMPLE_TICKETS, 1): print(f"Processing ticket {i}/{len(SAMPLE_TICKETS)}: {ticket['subject']}") try: send_ticket_to_agent(client, agent_id, ticket) except Exception as e: print(f"Failed: {e}") continue
if __name__ == "__main__": try: main() except Exception as e: print(f"Error: {e}") raiseCreate a file named test-ticket.ts and add the following code to it:
import Letta from '@letta-ai/letta-client';import * as dotenv from 'dotenv';
// Load environment variablesdotenv.config();
interface Ticket { customer_name: string; customer_email: string; subject: string; description: string; priority: string;}
// Sample test ticketsconst SAMPLE_TICKETS: Ticket[] = [ { customer_name: "John Doe", subject: "Cannot log in to dashboard", description: "I'm getting a 500 error when trying to log in. This is the third time this month.", priority: "High" }, { customer_name: "Sarah Smith", subject: "Question about billing", description: "I was charged twice for my subscription this month. Can someone look into this?", priority: "Medium" }, { customer_name: "Mike Johnson", subject: "Feature request", description: "Would love to see dark mode added to the app. Many users have been asking for this.", priority: "Low" }];
function formatTicketMessage(ticket: Ticket): string { /** * Format a ticket into a message for the Letta agent. */ return `New support ticket received:
Customer: ${ticket.customer_name} (${ticket.customer_email})Subject: ${ticket.subject}Description: ${ticket.description}Priority: ${ticket.priority}
Please analyze this ticket and provide:1. Recommended team to route to2. Adjusted priority level (if needed)3. Your reasoning based on customer history and patterns4. A suggested response message5. Estimated resolution time`;}
async function sendTicketToAgent(client: Letta, agentId: string, ticket: Ticket) { /** * Send a ticket to the Letta agent and get routing recommendation. */
// Send message to agent const messageContent = formatTicketMessage(ticket);
const response = await client.agents.messages.create(agentId, { messages: [ { role: "user", content: messageContent } ] });
// Extract and display the agent's response for (const message of response.messages) { if (message.message_type === 'assistant_message') { console.log((message as any).content); console.log(); } }
return response;}
async function main() { /** * Main test function. */
// Get credentials from environment const apiKey = process.env.LETTA_API_KEY; const agentId = process.env.LETTA_AGENT_ID;
if (!apiKey) { throw new Error("LETTA_API_KEY not set. Add it to your .env file."); }
if (!agentId) { throw new Error("LETTA_AGENT_ID not set. Run create-agent.ts first and add the ID to your .env file."); }
// Initialize client const client = new Letta({ apiKey: apiKey }); console.log("Connected to Letta Cloud"); console.log(`Using agent: ${agentId}\n`);
// Test with sample tickets for (let i = 0; i < SAMPLE_TICKETS.length; i++) { const ticket = SAMPLE_TICKETS[i]; console.log(`Processing ticket ${i + 1}/${SAMPLE_TICKETS.length}: ${ticket.subject}`);
try { await sendTicketToAgent(client, agentId, ticket); } catch (error: any) { console.error(`Failed: ${error.message}`); continue; } }}
// Run the scriptmain().catch((error) => { console.error(`Error: ${error.message}`); process.exit(1);});python test-ticket.pynpx tsx test-ticket.tsYou should see intelligent routing recommendations for each ticket:
Connected to Letta CloudUsing agent: agent-abc123def456
Processing ticket 1/3: Cannot log in to dashboardBased on the ticket analysis:
Recommended Team: Engineering - BackendPriority Level: HighReasoning: Customer John Doe has reported login issues...
Estimated Resolution Time: 2-4 hours
Processing ticket 2/3: Question about billing...If you see context-aware responses, your agent is working correctly.
You can also view your agent in Letta’s Agent Development Environment (ADE), a visual interface for managing and inspecting agents.
Go to https://app.letta.com and sign in with your Letta account.
You’ll see your agents list. Find your Support Ticket Router agent.

Click on the agent to open it in the ADE.

In the ADE, you can inspect your agent’s configuration, including its:
If you ran the test script, you can see the conversation history and responses in the chat interface.

The ADE provides full visibility into your agent’s configuration and state. When your n8n workflow runs, you can return to the ADE to see how the agent’s memory evolves as it processes real support tickets.
Learn more about the Agent Development Environment →Now we’ll connect your Letta agent to n8n to create the automated workflow.
Create a new workflow
In your n8n instance, click Create workflow or navigate to an existing workflow.

Add the Google Sheets trigger

Test the trigger

Now you’ll add the next step to your workflow. This step sends the ticket data to your Letta agent for analysis.
Add an HTTP request node
Configure the request method and URL
https://api.letta.com/v1/agents/YOUR_AGENT_ID_HERE/messagesReplace YOUR_AGENT_ID_HERE with the agent ID from Step 1.
Add the headers
Authorization for the Name and Bearer YOUR_LETTA_API_KEY for the Value. (Replace YOUR_LETTA_API_KEY with your actual Letta API key.)Content-Type for the Name and application/json for the Value.
Configure the request body
Toggle Send Body on.
In the Body Content Type dropdown, select Using JSON
In the Specify Body dropdown, select Using JSON
Click into the JSON field, then copy and paste the following contents of the request body:
{ "messages": [ { "role": "user", "content": "New support ticket received:\n\nCustomer: {{ $json['Customer Name'] }} ({{ $json['Customer Email'] }})\nSubject: {{ $json.Subject }}\nDescription: {{ $json.Description }}\nPriority: {{ $json.Priority }}\n\nPlease analyze this ticket and provide:\n1. Recommended team to route to\n2. Adjusted priority level (if needed)\n3. Your reasoning based on customer history and patterns\n4. A suggested response message\n5. Estimated resolution time" } ]}
Test the HTTP request
messages array.messages[0].content.You can also verify whether the test worked by checking your agent in the ADE at https://app.letta.com. You should see the test ticket in the conversation history.

Add a Slack node
Configure Slack authentication
Configure the Slack message
#support).*New Support Ticket*
*Customer:* {{ $('Google Sheets Trigger').item.json['Customer Name'] }} ({{ $('Google Sheets Trigger').item.json['Customer Email'] }})*Subject:* {{ $('Google Sheets Trigger').item.json.Subject }}*Description:* {{ $('Google Sheets Trigger').item.json.Description }}*Priority:* {{ $('Google Sheets Trigger').item.json.Priority }}
---
*Letta AI Recommendation:*
{{ $json.messages[0].content }}
---
View Ticket in SheetsTest the Slack message


Save your workflow
Click Save in the top right corner and name your workflow (for example, Intelligent Support Ticket Routing).
Activate the workflow
Toggle the workflow to Active using the toggle in the top right corner.

Your n8n workflow is now live and will process new support tickets automatically.
Add an example ticket to your Google Sheet:
| Ticket ID | Customer Name | Customer Email | Subject | Description | Priority |
|---|---|---|---|---|---|
| 1 | James Doe | [email protected] | Cannot access account | Getting an error when logging in | High |
Wait one to two minutes for n8n to detect the new row (the polling interval varies). You should receive a Slack notification with the following information:
Try adding another ticket from the same customer. The agent will remember the first interaction and provide context-aware routing based on the customer’s history.

Now that you’ve integrated a Letta agent into your n8n workflow, you can apply this same pattern to other use cases, such as lead qualification, content moderation, or expense approval.
Both n8n and Letta support self-hosted deployment, giving you full ownership of your automation infrastructure. We’ll cover how to self-host both platforms in a future guide.
Explore these guides to see how you can extend what you’ve built:
Custom tools
Learn how to create custom tools to extend your agent’s capabilities beyond message handling.
Memory management
Explore how to customize memory blocks for different workflow requirements.