Flag repeat customer contacts in Gorgias and alert Slack 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
  • SLACK_WEBHOOK_URL environment variable (Slack Incoming Webhook)
  • Python 3.9+ with requests installed

Overview

This agent skill scans your open Gorgias tickets, groups them by customer, and identifies anyone who has opened 3 or more tickets in the past 7 days. When it finds a repeat contact, it tags the latest ticket in Gorgias and posts a Slack alert with the customer's recent ticket history. You can run it on-demand or schedule it with cron.

Step 1: Create the skill directory

mkdir -p .claude/skills/repeat-contact/scripts

Step 2: Write the SKILL.md

Create .claude/skills/repeat-contact/SKILL.md:

---
name: repeat-contact
description: Scans Gorgias tickets for customers with 3+ tickets in the past 7 days, tags them as repeat contacts, and posts a Slack alert.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
 
Flag repeat customer contacts:
 
1. Run: `python $SKILL_DIR/scripts/flag_repeats.py`
2. Review the output — it shows flagged customers and their recent tickets
3. Adjust THRESHOLD or WINDOW_DAYS environment variables to tune sensitivity

Step 3: Write the detection script

Create .claude/skills/repeat-contact/scripts/flag_repeats.py:

#!/usr/bin/env python3
"""
Repeat Contact Flagging
Scans Gorgias tickets, identifies repeat contacts, tags them, and alerts Slack.
"""
import os
import json
from datetime import datetime, timedelta, timezone
 
try:
    import requests
except ImportError:
    os.system("pip install requests -q")
    import requests
 
GORGIAS_EMAIL  = os.environ["GORGIAS_EMAIL"]
GORGIAS_KEY    = os.environ["GORGIAS_API_KEY"]
GORGIAS_DOMAIN = os.environ["GORGIAS_DOMAIN"]
SLACK_WEBHOOK  = os.environ["SLACK_WEBHOOK_URL"]
BASE_URL       = f"https://{GORGIAS_DOMAIN}.gorgias.com/api"
AUTH           = (GORGIAS_EMAIL, GORGIAS_KEY)
 
THRESHOLD    = int(os.environ.get("REPEAT_THRESHOLD", "3"))
WINDOW_DAYS  = int(os.environ.get("REPEAT_WINDOW_DAYS", "7"))
 
 
def get_recent_tickets(limit: int = 100) -> list:
    """Fetch recent open and closed tickets within the detection window."""
    cutoff = datetime.now(timezone.utc) - timedelta(days=WINDOW_DAYS)
    resp = requests.get(
        f"{BASE_URL}/tickets",
        auth=AUTH,
        params={"limit": limit, "order_by": "created_datetime:desc"},
    )
    resp.raise_for_status()
    tickets = resp.json().get("data", [])
 
    return [
        t for t in tickets
        if datetime.fromisoformat(
            t["created_datetime"].replace("Z", "+00:00")
        ) >= cutoff
    ]
 
 
def group_by_customer(tickets: list) -> dict:
    """Group tickets by customer ID."""
    groups = {}
    for ticket in tickets:
        cust = ticket.get("customer")
        if not cust or not cust.get("id"):
            continue
        cid = cust["id"]
        if cid not in groups:
            groups[cid] = {
                "customer_id": cid,
                "name": cust.get("firstname", cust.get("email", "Unknown")),
                "email": cust.get("email", "unknown"),
                "tickets": [],
            }
        groups[cid]["tickets"].append({
            "id": ticket["id"],
            "subject": ticket.get("subject", "(no subject)"),
            "status": ticket.get("status", "unknown"),
            "created": ticket["created_datetime"],
        })
    return groups
 
 
def tag_ticket(ticket_id: int, tag: str = "repeat-contact") -> None:
    """Apply a tag to a ticket in Gorgias."""
    resp = requests.put(
        f"{BASE_URL}/tickets/{ticket_id}",
        auth=AUTH,
        json={"tags": [{"name": tag}]},
    )
    resp.raise_for_status()
 
 
def send_slack_alert(customer: dict) -> None:
    """Post a repeat contact alert to Slack."""
    tickets_list = "\n".join(
        f"  - #{t['id']}: {t['subject']} ({t['status']})"
        for t in customer["tickets"]
    )
    latest = customer["tickets"][0]
    text = (
        f":rotating_light: *Repeat Contact Flagged*\n\n"
        f"*{customer['name']}* ({customer['email']}) has opened "
        f"*{len(customer['tickets'])} tickets* in the last {WINDOW_DAYS} days.\n\n"
        f"*Recent tickets:*\n{tickets_list}\n\n"
        f"<https://{GORGIAS_DOMAIN}.gorgias.com/app/ticket/{latest['id']}"
        f"|View latest ticket in Gorgias>"
    )
    requests.post(SLACK_WEBHOOK, json={"text": text})
 
 
def main() -> None:
    print(f"Scanning for customers with {THRESHOLD}+ tickets "
          f"in the last {WINDOW_DAYS} days...\n")
 
    tickets = get_recent_tickets()
    print(f"Fetched {len(tickets)} tickets from the last {WINDOW_DAYS} days")
 
    groups = group_by_customer(tickets)
    flagged = {
        cid: data for cid, data in groups.items()
        if len(data["tickets"]) >= THRESHOLD
    }
 
    if not flagged:
        print("No repeat contacts found. All clear.")
        return
 
    print(f"Found {len(flagged)} repeat contact(s):\n")
 
    for cid, customer in flagged.items():
        latest_ticket = customer["tickets"][0]
 
        # Tag the most recent ticket
        tag_ticket(latest_ticket["id"])
 
        # Alert Slack
        send_slack_alert(customer)
 
        print(f"  {customer['name']} ({customer['email']})")
        print(f"    {len(customer['tickets'])} tickets in {WINDOW_DAYS} days")
        for t in customer["tickets"]:
            print(f"      #{t['id']}: {t['subject']} ({t['status']})")
        print(f"    -> Tagged #{latest_ticket['id']} + Slack alert sent\n")
 
    print(f"Done. Flagged {len(flagged)} repeat contact(s).")
 
 
if __name__ == "__main__":
    main()
The script tags the most recent ticket only

Tagging every ticket from a repeat contact creates noise. The script tags only the latest ticket so your team sees the flag on the one they're most likely to open next. Previous tickets are listed in the Slack alert for context.

Step 4: Run the skill

# Via Claude Code
/repeat-contact
 
# Or run directly
python .claude/skills/repeat-contact/scripts/flag_repeats.py
 
# With custom threshold (e.g., 5 tickets in 14 days)
REPEAT_THRESHOLD=5 REPEAT_WINDOW_DAYS=14 python .claude/skills/repeat-contact/scripts/flag_repeats.py

A typical run looks like:

Scanning for customers with 3+ tickets in the last 7 days...
 
Fetched 87 tickets from the last 7 days
Found 2 repeat contact(s):
 
  Sarah M. (sarah@example.com)
    4 tickets in 7 days
      #14501: Can't log in to my account (open)
      #14487: Still can't access order history (open)
      #14463: Password reset not working (closed)
      #14451: Account access issue (closed)
    -> Tagged #14501 + Slack alert sent
 
  James K. (james@example.com)
    3 tickets in 7 days
      #14498: Wrong item received again (open)
      #14472: Missing item in my order (closed)
      #14460: Shipping damage (closed)
    -> Tagged #14498 + Slack alert sent
 
Done. Flagged 2 repeat contact(s).

Step 5: Schedule it

# crontab -e — every 2 hours during business hours
0 8-18/2 * * 1-5 cd /path/to/project && python .claude/skills/repeat-contact/scripts/flag_repeats.py
Run frequency depends on your urgency

Every 2 hours is a good starting point. If your team needs faster notification, switch to the n8n approach with a webhook trigger for real-time detection. The agent skill approach is better suited for periodic sweeps.

Step 6: Tune the threshold over time

After a week of running, review the flagged contacts:

  • Too many false positives? Raise REPEAT_THRESHOLD to 4 or 5
  • Missing genuine repeat contacts? Lower to 2 or shorten REPEAT_WINDOW_DAYS
  • Certain customer segments always flag? Add exclusions for known high-volume accounts (agencies, resellers) by maintaining an exclude list in the script

Cost

  • Gorgias API calls: 2 per run (list tickets + tag per flagged customer)
  • Slack webhook: free
  • Typical run: under 5 seconds, no external AI API calls needed
  • Scheduled 5x/day: ~10 Gorgias API calls/day

Need help implementing this?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.