Automate first responses to Gorgias tickets using an agent skill
Prerequisites
- Claude Code, Cursor, or another AI coding agent that supports skills
GORGIAS_EMAIL,GORGIAS_API_KEY,GORGIAS_DOMAINenvironment variablesANTHROPIC_API_KEYfor Claude-drafted responses- A company knowledge base or FAQ text file Claude can reference for accurate answers
Overview
This agent skill finds Gorgias tickets that have no agent reply yet, drafts a contextual first response using Claude, and posts it back to Gorgias. Unlike static Macros, Claude-drafted replies can incorporate nuance from the customer's message — matching tone, referencing specific details they mentioned, and pulling from a knowledge base you provide.
Step 1: Create the skill directory
mkdir -p .claude/skills/first-response/scriptsStep 2: Create a knowledge base file
Create .claude/skills/first-response/knowledge-base.md with your support FAQs:
# Support Knowledge Base
## Order Tracking
- Tracking emails are sent within 24 hours of shipping
- Track at: https://yourstore.com/tracking
- Orders ship in 1-2 business days; delivery takes 3-5 business days
## Returns
- 30-day return window from delivery date
- Items must be unused and in original packaging
- Start a return at: https://yourstore.com/returns
- Refunds process within 5-7 business days after we receive the item
## Order Changes and Cancellations
- Orders can be changed or cancelled within 1 hour of placement
- After 1 hour, contact us immediately — we'll try our best but can't guarantee changes
- Email: support@yourstore.com
## International Shipping
- We ship to 40+ countries
- International delivery: 7-14 business days
- Customs fees are the customer's responsibilityUpdate this file whenever your policies change. Claude uses it to generate accurate, policy-compliant replies.
Step 3: Write the SKILL.md
Create .claude/skills/first-response/SKILL.md:
---
name: first-response
description: Finds Gorgias tickets with no agent reply and drafts a contextual first response using Claude, posted as an internal note for agent review or sent directly.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Draft first responses for tickets awaiting a reply:
1. Run: `python $SKILL_DIR/scripts/respond.py`
2. Review the output — it shows each ticket and the draft response
3. Responses are posted as internal notes by default; edit the script to auto-sendStep 4: Write the response script
Create .claude/skills/first-response/scripts/respond.py:
#!/usr/bin/env python3
"""
Gorgias First Response Automation
Finds tickets with no agent reply → drafts response with Claude → posts to Gorgias.
"""
import os
from pathlib import Path
try:
import requests
from anthropic import Anthropic
except ImportError:
os.system("pip install requests anthropic -q")
import requests
from anthropic import Anthropic
GORGIAS_EMAIL = os.environ["GORGIAS_EMAIL"]
GORGIAS_KEY = os.environ["GORGIAS_API_KEY"]
GORGIAS_DOMAIN = os.environ["GORGIAS_DOMAIN"]
BASE_URL = f"https://{GORGIAS_DOMAIN}.gorgias.com/api"
AUTH = (GORGIAS_EMAIL, GORGIAS_KEY)
SKILL_DIR = Path(__file__).parent.parent
KB_PATH = SKILL_DIR / "knowledge-base.md"
KNOWLEDGE = KB_PATH.read_text() if KB_PATH.exists() else ""
# Set to True to send as a public reply instead of an internal note
AUTO_SEND = os.environ.get("AUTO_SEND", "false").lower() == "true"
client = Anthropic()
def get_tickets_needing_reply(limit: int = 20) -> list:
resp = requests.get(
f"{BASE_URL}/tickets",
auth=AUTH,
params={"status": "open", "limit": limit},
)
resp.raise_for_status()
tickets = resp.json().get("data", [])
result = []
for ticket in tickets:
messages = ticket.get("messages", [])
# Only include tickets where all messages are from the customer
has_agent_reply = any(
m.get("source", {}).get("type") == "helpdesk" for m in messages
)
if not has_agent_reply and messages:
result.append(ticket)
return result
def draft_response(customer_name: str, subject: str, body: str) -> str:
prompt = f"""You are a friendly, concise customer support agent for an e-commerce store.
Use the knowledge base below to write a helpful first response to this customer's message.
Keep the response under 150 words. Be warm but efficient. Don't over-apologize.
Knowledge base:
{KNOWLEDGE}
Customer name: {customer_name or 'there'}
Subject: {subject}
Message: {body[:800]}
Write only the reply body — no subject line, no "Dear", just the message content starting with "Hi [name],"."""
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=300,
messages=[{"role": "user", "content": prompt}],
)
return message.content[0].text.strip()
def post_response(ticket_id: int, body: str, as_public: bool = False) -> None:
payload = {
"body_text": body,
"body_html": f"<p>{body.replace(chr(10), '</p><p>')}</p>",
"channel": "email",
"from_agent": True,
"public": as_public,
"source": {
"type": "helpdesk",
"from": {"name": "Support Team", "address": f"support@{GORGIAS_DOMAIN}.com"},
},
}
resp = requests.post(f"{BASE_URL}/tickets/{ticket_id}/messages", auth=AUTH, json=payload)
resp.raise_for_status()
def main() -> None:
print("Looking for tickets with no agent reply...")
tickets = get_tickets_needing_reply()
print(f"Found {len(tickets)} ticket(s) needing a first response\n")
if not tickets:
print("All caught up.")
return
for ticket in tickets:
messages = ticket.get("messages", [])
subject = ticket.get("subject", "")
body = messages[0].get("body_text", "") if messages else ""
name = ticket.get("requester", {}).get("firstname", "")
draft = draft_response(name, subject, body)
post_response(ticket["id"], draft, as_public=AUTO_SEND)
mode = "sent" if AUTO_SEND else "posted as internal note"
print(f" #{ticket['id']} {subject[:50]!r} → {mode}")
print(f" Draft: {draft[:100]}...\n")
print(f"Done. Processed {len(tickets)} ticket(s).")
if __name__ == "__main__":
main()Step 5: Run the skill
# Via Claude Code
/first-response
# Auto-send mode (skip internal note — send directly to customer)
AUTO_SEND=true python .claude/skills/first-response/scripts/respond.py
# Default — posts as internal note for agent review
python .claude/skills/first-response/scripts/respond.pyRun with AUTO_SEND=false (the default) for the first week. Review the internal notes and verify response quality before switching to auto-send. This builds confidence without risk of sending a bad reply.
Step 6: Schedule it
# crontab -e — every 30 minutes during business hours
*/30 8-18 * * 1-5 cd /path/to/project && python .claude/skills/first-response/scripts/respond.pyCost
- Claude Haiku: ~$0.003 per drafted response (reading ticket + generating reply)
- 300 drafts/month ≈ $0.90 in API costs
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.