Abandoned Cart Recovery

Query checkouts abandoned in the last N days, generate unique discount codes per customer, and tag them for re-engagement.

shopify-admin-abandoned-cart-recovery


Purpose

Identifies customers who started checkout but did not complete their purchase, generates a unique discount code for each one, and tags them in Shopify so they can be targeted in follow-up campaigns. This skill handles the Shopify-native data layer (querying, discounts, tagging); sending the actual email requires an external tool.


Prerequisites

  • Authenticated Shopify CLI session: shopify auth login --store
  • API scopes: read_checkouts, write_price_rules, write_discount_codes, write_customers

  • Parameters


    ParameterTypeRequiredDefaultDescription
    storestringyesStore domain (e.g., mystore.myshopify.com)
    formatstringnohumanOutput format: human or json
    dry_runboolnofalsePreview operations without executing mutations
    days_backintegerno7Lookback window for abandoned checkouts
    min_cart_valuefloatno0Minimum cart total to include (USD)
    discount_pctintegerno10Discount percentage to create per customer
    code_prefixstringnoRECOVERPrefix for generated discount codes
    tagstringnocart-recoveryTag applied to eligible customers

    Safety


    > ⚠️ Steps 2 and 3 execute mutations (discount code creation, customer tagging). Discount codes created via discountCodeBulkCreate cannot be bulk-deleted via the Admin API — they must be removed individually or via the price rule. Run with dry_run: true to verify eligible customer count before committing.


    Workflow Steps


  • OPERATION: abandonedCheckouts — query
  • Inputs: first: 250, query: "created_at:>=''", pagination cursor

    Expected output: List of checkout objects with email, totalPrice, createdAt, lineItems; paginate until hasNextPage: false


  • OPERATION: discountCodeBulkCreate — mutation
  • Inputs: For each eligible customer email: a unique code {code_prefix}-{UUID[:8].toUpperCase()}, percentage discount discount_pct, usage limit 1, expiry 30 days

    Expected output: Confirmation of code creation with code and priceRule.id; collect userErrors


  • OPERATION: tagsAdd — mutation
  • Inputs: Customer id (from checkout email lookup), tag string from tag parameter

    Expected output: Updated customer tags; collect userErrors


    GraphQL Operations


    # abandonedCheckouts:query — validated against api_version 2025-04
    query AbandonedCheckouts($first: Int!, $after: String, $query: String) {
      abandonedCheckouts(first: $first, after: $after, query: $query) {
        edges {
          node {
            id
            customer {
              defaultEmailAddress {
                emailAddress
              }
            }
            totalPriceSet {
              shopMoney {
                amount
                currencyCode
              }
            }
            createdAt
            lineItems {
              edges {
                node {
                  title
                  quantity
                  variant {
                    price
                  }
                }
              }
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
    

    # discountCodeBulkCreate:mutation — validated against api_version 2025-01
    mutation DiscountCodeBulkCreate($priceRuleId: ID!, $codes: [DiscountCodeInput!]!) {
      discountCodeBulkCreate(priceRuleId: $priceRuleId, codes: $codes) {
        bulkCreations {
          id
          done
        }
        userErrors {
          field
          message
        }
      }
    }
    

    # tagsAdd:mutation — validated against api_version 2025-01
    mutation TagsAdd($id: ID!, $tags: [String!]!) {
      tagsAdd(id: $id, tags: $tags) {
        node {
          id
        }
        userErrors {
          field
          message
        }
      }
    }
    

    Session Tracking


    Claude MUST emit the following output at each stage. This is mandatory.


    On start, emit:

    ╔══════════════════════════════════════════════╗
    ║  SKILL: Abandoned Cart Recovery              ║
    ║  Store: <store domain>                       ║
    ║  Started: <YYYY-MM-DD HH:MM UTC>             ║
    ╚══════════════════════════════════════════════╝
    

    After each step, emit:

    [N/TOTAL] <QUERY|MUTATION>  <OperationName>
              → Params: <brief summary of key inputs>
              → Result: <count or outcome>
    

    If dry_run: true, prefix every mutation step with [DRY RUN] and do not execute it.


    On completion, emit:


    For format: human (default):

    ══════════════════════════════════════════════
    OUTCOME SUMMARY
      Checkouts found:     <n>
      Eligible customers:  <n>
      Codes created:       <n>
      Customers tagged:    <n>
      Errors:              <n>
      Output:              recovery_list_<date>.csv
    ══════════════════════════════════════════════
    

    For format: json, emit:

    {
      "skill": "abandoned-cart-recovery",
      "store": "<domain>",
      "started_at": "<ISO8601>",
      "completed_at": "<ISO8601>",
      "dry_run": false,
      "steps": [
        { "step": 1, "operation": "AbandonedCheckouts", "type": "query", "params_summary": "last 7 days, cart >= $0", "result_summary": "<n> checkouts", "skipped": false },
        { "step": 2, "operation": "DiscountCodeBulkCreate", "type": "mutation", "params_summary": "<n> codes, 10% off, prefix RECOVER", "result_summary": "<n> created", "skipped": false },
        { "step": 3, "operation": "TagsAdd", "type": "mutation", "params_summary": "tag: cart-recovery", "result_summary": "<n> customers tagged", "skipped": false }
      ],
      "outcome": {
        "checkouts_found": 0,
        "eligible_customers": 0,
        "codes_created": 0,
        "customers_tagged": 0,
        "errors": 0,
        "output_file": "recovery_list_<date>.csv"
      }
    }
    

    Output Format

    CSV file recovery_list_.csv with columns:

    customer_email, cart_total, abandoned_at, discount_code, discount_pct, tag_applied


    Error Handling

    ErrorCauseRecovery
    THROTTLEDAPI rate limit exceededWait 2 seconds, retry up to 3 times
    userErrors on discount creationInvalid price rule or duplicate codeLog error, skip customer, continue
    userErrors on tagsAddCustomer ID not foundLog error, skip tag, continue
    No email on checkoutGuest checkout without emailSkip checkout, do not count as eligible

    Best Practices

  • Run with dry_run: true first and confirm eligible customer count — discount codes cannot be bulk-deleted via API and will persist in your price rules until manually removed.
  • Set min_cart_value to focus recovery effort on higher-value carts (e.g., 50 for $50+). Small carts often have lower recovery intent and may not justify a discount.
  • Use a unique tag per run (e.g., cart-recovery-2026-04-11) so you can identify exactly which customers were targeted in each campaign and avoid re-targeting them in subsequent runs.
  • The code_prefix should be meaningful to operators reviewing discount codes in Shopify Admin (e.g., CART10 for a 10% cart recovery discount).
  • This skill produces the data layer. Use Shopify Email or another email tool to send the generated codes to customers.