E-commerce automation guide

How to Automate Customer Service Email Triage for Shopify with n8n + Claude (2026)

A field-tested blueprint for routing every inbound ticket — refunds, shipping, sizing, fraud — through a Claude classifier, a Shopify Admin GraphQL lookup, and a Gorgias handoff queue. Code, prompts, and the failure modes nobody tells you about.

13 min read
Updated May 2026
Intermediate-Advanced

The problem: 600 tickets a week, 80% are five questions on repeat

Every Shopify merchant past a few hundred orders per month hits the same wall. Your inbox, your Gorgias queue, your Shopify Inbox forwards, and your WhatsApp Business number all converge on the same overworked team. Roughly 80% of what they answer is the same five questions: where is my order, can I return this, do you have it in a medium, can I change the address, this arrived broken. The remaining 20% — chargebacks, fraud, escalations, B2B inquiries — actually needs a human brain.

The fix is not a chatbot bolted onto your storefront. The fix is a triage layer that intercepts every channel, looks up the order in Shopify, classifies the intent with a real LLM, drafts a reply that cites real shipment data, and only escalates the genuinely hard ones. This guide walks through that pipeline end-to-end with n8n, Claude, Shopify Admin GraphQL, Gorgias, and Klaviyo — including the privacy guardrails that keep PCI DSS auditors happy.

If you also run brick-and-mortar or showroom traffic, the companion retail automation playbook covers POS-side triage; for booking-driven brands the hospitality guide handles reservation logic.

Architecture: seven nodes, one queue, zero leaked PANs

Every inbound message becomes an n8n execution. The execution must be idempotent (a retried webhook can’t double-refund a customer) and side-effect-free until the final branch fires. Here is the canonical flow.

Inbox / Webhook

Gmail, Gorgias, WA

Order Lookup

Shopify GraphQL

Classify

Claude intent

Auto-Reply

Templated draft

Sentiment Check

Score < 0.3 ?

Handoff / Resolve

Gorgias agent

Klaviyo Event

Post-purchase flow

01

Unify the channels — one webhook to rule them all

Most stores have at least four inbound channels: a shared Gmail or Outlook inbox ([email protected]), a Gorgias instance receiving Shopify Inbox forwards, a WhatsApp Business number, and the contact form on the storefront. The first job is normalising every one of them into a single n8n webhook payload so the rest of the pipeline doesn’t care where the message came from.

Use n8n’s native Gmail trigger for the inbox, a Gorgias webhook trigger (configure it inside Gorgias under Settings → REST API → Add HTTP Integration), and a Twilio WhatsApp webhook for messaging. Each one transforms into the same canonical envelope:

canonical-message.json
{
  "channel": "gmail | gorgias | whatsapp | form",
  "external_id": "thread-or-message-id",
  "received_at": "2026-05-03T09:14:22Z",
  "from": {
    "email": "[email protected]",
    "phone": "+15551234567",
    "name": "Jane Buyer"
  },
  "subject": "Where is my order?",
  "body_text": "Hi, I ordered last Tuesday and...",
  "body_html": null,
  "attachments": [],
  "store_id": "shop-us-main"
}

Insight: The Gmail trigger polls every 60 seconds by default. For a sub-30-second first-response, switch to push notifications via Gmail’s Pub/Sub watch — it’s a one-time setup and pays for itself the first time a customer tweets about how fast you replied.

De-duplicate aggressively. Buyers forward, reply-all, and CC themselves. Hash external_id + from.email in a Postgres table and skip anything you’ve seen in the last 60 seconds.

02

Order lookup — Shopify Admin GraphQL, the right way

Before any classification, enrich the message with order context. Extract candidate identifiers from the body — order number (#1234, SHOP-1234), email, phone — using a small regex node, then query Shopify Admin GraphQL. Always GraphQL, never REST: REST is rate-limited per call (2/sec), GraphQL is rate-limited by query cost (50 points/sec) and you can fetch order, fulfillment, and refunds in a single trip.

order-lookup.graphql
query OrderContext($q: String!) {
  orders(first: 1, query: $q, sortKey: CREATED_AT, reverse: true) {
    edges {
      node {
        id
        name
        email
        phone
        createdAt
        displayFinancialStatus
        displayFulfillmentStatus
        totalPriceSet { presentmentMoney { amount currencyCode } }
        customer { id numberOfOrders tags }
        shippingAddress { city provinceCode countryCode }
        fulfillments(first: 5) {
          status
          trackingInfo { number url company }
          updatedAt
        }
        refunds(first: 5) {
          createdAt
          totalRefundedSet { presentmentMoney { amount } }
        }
      }
    }
  }
}

Pass q as email:[email protected] or name:#1234. If no order matches, set order_context: null and let the classifier handle it as a pre-purchase question.

Watch out: Shopify GraphQL throttles by query cost, not by request count. A naïve nested query can cost 200+ points and lock you out for 4 seconds. Use extensions.cost.requestedQueryCost in dev to keep every query under 50.

For WooCommerce stores, swap the query for a GET /wp-json/wc/v3/orders?search=... call and merge the shape; for BigCommerce use GET /v2/orders?email=.... The classifier downstream doesn’t care which backend produced the context — only that the shape matches.

03

Intent classifier — Claude with a tight schema

The classifier is the brain. We send Claude the message body plus a redacted order summary and ask for a single JSON object with a fixed enum of intents. No free-form output, no creative interpretation. The schema below is what we run in production.

classifier-system-prompt.txt
You are a customer-service triage classifier for a Shopify store.
Return ONLY a JSON object matching this schema. No prose.

{
  "intent": "refund_return | shipping_tracking | product_question
           | sizing_fit | defective_damaged | lost_order
           | change_address | cancel_order | fraud_suspect | other",
  "confidence": 0.0-1.0,
  "sentiment": -1.0 to 1.0,
  "urgency": "low | normal | high | critical",
  "language": "ISO-639-1",
  "requires_human": boolean,
  "rationale": "max 30 words, plain English"
}

Rules:
- If order_context.totalPrice > 500 USD, set requires_human = true.
- If sentiment < -0.4, set requires_human = true.
- If intent = fraud_suspect, ALWAYS requires_human = true.
- If confidence < 0.85, set requires_human = true.
- Never invent order numbers, tracking numbers, or refund amounts.

Use Claude Sonnet for this — Haiku is too literal with sentiment, Opus is overkill at the volume. Pin the model version (claude-sonnet-4-7@20260301 or whichever is current) and treat it like a contract. Schema drift will silently break your downstream branches.

Critical: NEVER include the customer's full credit card number in the prompt to Claude. Shopify exposes only the last 4 digits via paymentDetails.creditCardLastDigits — that's all the LLM ever sees. PCI DSS scope creep is a multi-figure mistake.

Validate the JSON output with a strict schema before routing. If parsing fails (it will, occasionally) fall back to requires_human=true, intent=other and let a human triage it.

04

Auto-response branch — drafts that cite real data

For every intent with confidence ≥ 0.85 and requires_human = false, generate a personalized reply that cites the actual order. The trick is to compose the reply from two layers: a hand-written template per intent, and Claude's job is to fill the slots — not to write English from scratch.

reply-templates.json
{
  "shipping_tracking": {
    "subject": "Your order {{order.name}} — tracking update",
    "body": "Hi {{customer.firstName}},nnYour order {{order.name}} shipped on {{fulfillment.updatedAt | date}} via {{fulfillment.company}}. Tracking: {{fulfillment.trackingNumber}}nnLatest status from the carrier: {{carrier.status}} (ETA {{carrier.eta | date}}).nn{{closing}}",
    "max_confidence_required": 0.90
  },
  "refund_return": {
    "subject": "Return for order {{order.name}}",
    "body": "Hi {{customer.firstName}},nnYour order was placed on {{order.createdAt | date}}, which is {{daysSincePurchase}} days ago. Our return window is 30 days, so you're {{returnEligible ? 'eligible' : 'past the window'}}.nn{{instructionsBlock}}",
    "max_confidence_required": 0.92
  }
}

For shipping queries, do a parallel call to the carrier API (EasyPost wraps UPS, USPS, FedEx, DHL behind one endpoint) so the reply contains a live status, not a stale "shipped on Tuesday." For refunds, compute eligibility against the return window in n8n — never let the LLM decide policy.

Insight: Send via Postmark or SendGrid, not via Gmail's send API. You want bounce and complaint webhooks routed back into the same pipeline so a hard-bounced auto-reply triggers a human review instead of vanishing into the void.
05

Human handoff — context bundle, suggested reply, calm priority

Anything flagged requires_human = true goes to Gorgias as a new ticket with a structured internal note. The note is the difference between a 12-minute resolution and a 4-minute one — give the agent everything they would have looked up themselves, plus a draft reply they can accept, edit, or discard.

gorgias-handoff.json
POST https://YOURSTORE.gorgias.com/api/tickets
Authorization: Basic {{base64(email:apikey)}}

{
  "channel": "email",
  "from_agent": false,
  "customer": {"email": "{{from.email}}"},
  "subject": "{{subject}}",
  "tags": ["ai-triaged", "{{intent}}", "urgency-{{urgency}}"],
  "messages": [{
    "channel": "email",
    "from_agent": false,
    "sender": {"email": "{{from.email}}"},
    "body_text": "{{body_text}}"
  }],
  "meta": {
    "ai_intent": "{{intent}}",
    "ai_confidence": "{{confidence}}",
    "ai_sentiment": "{{sentiment}}",
    "order_name": "{{order.name}}",
    "order_total": "{{order.totalPrice}}",
    "lifetime_orders": "{{customer.numberOfOrders}}",
    "suggested_reply": "{{draftedReply}}"
  }
}

Security: Store the Gorgias API key in n8n's credentials vault, not in node parameters. Rotate quarterly. Audit the OAuth scopes — Gorgias doesn't need tickets:delete for this workflow, only tickets:write.

Route by intent: fraud and chargebacks to a dedicated reviewer, sizing/fit to the merchandising team if you have one, the rest to general queue. Use Gorgias' "rules" engine to enforce SLAs based on the urgency tag.

06

Klaviyo event push — close the loop, fuel the next campaign

Every resolved ticket pushes a custom event into Klaviyo. This event is the trigger for win-back flows after refunds, post-purchase NPS asks after happy resolutions, and a "we've fixed it" check-in 7 days after a defective-product replacement. The event payload should be lean and queryable.

klaviyo-event.json
POST https://a.klaviyo.com/api/events/
Authorization: Klaviyo-API-Key {{KLAVIYO_PRIVATE_KEY}}
revision: 2026-04-15

{
  "data": {
    "type": "event",
    "attributes": {
      "metric": {"data": {"type": "metric", "attributes": {"name": "CS Ticket Resolved"}}},
      "profile": {"data": {"type": "profile", "attributes": {"email": "{{from.email}}"}}},
      "properties": {
        "intent": "{{intent}}",
        "outcome": "{{outcome}}",
        "resolved_by": "{{ai|human}}",
        "first_response_seconds": {{firstResponseSeconds}},
        "order_name": "{{order.name}}"
      },
      "time": "{{resolvedAt}}"
    }
  }
}

In Klaviyo, build flows that branch on the intent property: refund_return triggers a 14-day win-back; defective_damaged triggers a quality-assurance follow-up; shipping_tracking + resolved_by=ai goes into a "first-time positive AI experience" segment for the brand team to tag. Respect email_consent — Klaviyo enforces this, but n8n should pre-check before the POST to avoid wasted API calls.

Common failure modes and how we caught them in production

Every team building this hits the same six bugs. Here is the short list, ordered by how much money they cost before we noticed.

Idempotency drift on retries

Webhook retries fired the refund branch twice. Fix: a Postgres processed_messages table keyed on (channel, external_id) with a 7-day TTL.

GraphQL cost spikes

A nested fulfillments(first: 50) blew the budget. Fix: cap to first 5 and paginate only on demand.

Sentiment false-positives in Hebrew

Polite Hebrew customer-service language scored as negative. Fix: pass language hint into the system prompt.

Auto-replies to no-reply addresses

Bounce loops. Fix: maintain a regex blocklist (noreply@, postmaster@, mailer-daemon@) before sending.

Stale carrier ETAs

Cached EasyPost responses showed 3-day-old ETAs. Fix: 15-minute cache TTL with explicit refresh=true on customer reply.

Schema drift after model upgrade

A pinned model deprecated mid-quarter; default rolled forward and broke our enum. Fix: regression suite of 200 historical tickets in CI.

Privacy & compliance — the non-negotiables

A customer-service pipeline touches PII on every execution. The compliance regime depends on where your buyers live, but four standards always apply.

RegimeApplies whenWhat it forces you to do
PCI DSSAlwaysNever pass full card PAN to the LLM. Last-4 only via Shopify.
GDPRAny EU/UK buyerDSAR endpoint, 30-day deletion, lawful basis logged per profile.
CCPA / CPRACA buyers"Do Not Sell" honored; per-event opt-out flag in Klaviyo.
CAN-SPAMAny US buyerPhysical address + working unsubscribe in every auto-reply.
Shopify ToSAlways2 req/sec REST, 50 cost-points/sec GraphQL, no scraping admin UI.

Anthropic's API does not train on your inputs by default, but log retention is 30 days. If your DPA requires zero retention, request the zero-data-retention amendment before go-live. Klaviyo's consent rules are stricter than the law in most jurisdictions — never push events for un-consented profiles.

Results — what a 12-month rollout looked like

Numbers from a US-based apparel brand running roughly 800 inbound tickets per week across Shopify Plus, Gorgias, and WhatsApp Business. Twelve months from go-live.

MetricBeforeAfterDelta
Avg. first-response time3 h 42 min12 sec−99.9%
Tickets resolved without human0%65%+65 pts
Annual CS labor cost$148K (2 FTE)$58K (0.7 FTE)−$90K / yr
Refund-related cost8.1% of revenue4.9%−3.2 pts
NPS (post-CS interaction)+24+42+18

The refund-cost number is worth dwelling on: proactive shipping updates (Step 4) intercepted enough "where is my order" panic to suppress a measurable share of speculative chargebacks.

Four-week implementation timeline

WEEK 1

Audit & access

Shopify Admin API token, Gorgias REST creds, sample 1000 historical tickets, classify by hand to build the gold set.

WEEK 2

Classifier + prompts

Wire n8n webhooks, prompt-engineer Claude against the 1000-ticket gold set, target ≥92% intent accuracy.

WEEK 3

Gorgias + reply templates

Build handoff bundle, draft templates per intent, sentiment fallback, internal QA on shadow traffic.

WEEK 4

Klaviyo + go-live

Wire Klaviyo events, launch behind a manual approval queue, watch metrics for 5 days, then unlock auto-send for high-confidence intents.

Frequently asked questions

Will customers know they're talking to AI?
For shipping, sizing, and tracking templates — usually no, because the reply is indistinguishable from what a fast human agent would type. We recommend a soft disclosure in your footer ("Replies may be drafted with AI assistance and reviewed by our team") rather than a banner on every email. Disclosure becomes mandatory in some jurisdictions (Italy, parts of California for material decisions) — your legal counsel should map this to your buyer geography.
What about WooCommerce or BigCommerce?
Same architecture, different lookup node. Replace the Shopify Admin GraphQL call with the WooCommerce REST API (/wp-json/wc/v3/orders) or BigCommerce v3 Catalog/Orders API. The classifier, reply templates, and Gorgias handoff are platform-agnostic. Watch out: WooCommerce rate limits depend on the host (often nginx-level), and BigCommerce enforces per-store hourly caps.
How do you handle international shipping questions across carriers?
EasyPost and Shippo both wrap UPS, USPS, FedEx, DHL, Royal Mail, Australia Post, Israel Post, and ~80 regional carriers behind one tracking endpoint. We call EasyPost from the auto-reply branch, normalise the status into a five-state enum (label_created, in_transit, out_for_delivery, delivered, exception), and let the template render per language. For carriers EasyPost doesn't support, fall back to a "we've contacted the carrier" template and route to human.
What if Claude offers a refund outside policy?
It can't, because the LLM never decides policy. The refund-eligibility check is a pure n8n function comparing order.createdAt against your return-window constant. The reply template only renders the eligible/ineligible branch — Claude fills the personalisation slots, the deterministic code holds the policy line. This is the single most important guardrail in the whole pipeline.
Trial without replacing our Gorgias setup?
Yes — most teams keep Gorgias as the system of record and wire the n8n pipeline as a pre-processor. The flow becomes: inbound email → n8n classifier → either auto-reply via Postmark and close in Gorgias programmatically, or push to Gorgias as a tagged ticket with a suggested reply for the agent to accept. Your team sees Gorgias on day one exactly as before, with a "AI-Suggested Reply" panel.
How does this compare to Gorgias Auto-Reply or Tidio Lyro?
Gorgias Auto-Reply and Tidio Lyro are good at FAQ deflection — they're closed-source and work inside their own UI. The n8n + Claude approach trades that turnkey simplicity for three things you cannot get out-of-the-box: (1) order-aware replies that cite the actual fulfillment status, (2) custom policy enforcement (B2B pricing tiers, regional return windows, loyalty exceptions), and (3) data ownership — every ticket, prompt, and decision lives in your Postgres. For under ~300 tickets/week, the SaaS option may be cheaper. Past that, the custom build wins on cost and on conversion.

Ready to ship this in your store?

We've built this exact pipeline for Shopify Plus brands, DTC apparel, beauty, and home-goods stores. Four weeks to live, fixed-price, your data stays in your stack.

Talk to an e-commerce automation engineer

See also: E-commerce AI services · Beauty & cosmetics · All AI services