Automate first responses to Gorgias tickets using an agent skill

low complexityCost: Usage-based

Prerequisites

Prerequisites
  • Claude Code, Cursor, or another AI coding agent that supports skills
  • GORGIAS_EMAIL, GORGIAS_API_KEY, GORGIAS_DOMAIN environment variables
  • ANTHROPIC_API_KEY for 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/scripts

Step 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 responsibility

Update 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-send

Step 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.py
Start with internal notes

Run 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.py

Cost

  • 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.