Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Diagnose and fix Customer.io common errors. Use when troubleshooting API errors, delivery failures, campaign issues, or SDK exceptions. Trigger: "customer.io error", "customer.io not working", "debug customer.io", "customer.io 401", "customer.io 429".
.claude/skills/jeremylongshore-customerio-common-errors/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 132% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 105% | 0% |
Diagnose and fix the most frequent Customer.io integration errors: API status codes, SDK exceptions, delivery failures, campaign trigger issues, and transactional message problems.
| Code | Meaning | Retryable | Action | |------|---------|-----------|--------| | 200 | Success | N/A | No action needed | | 400 | Bad Request | No | Fix request payload — see details below | | 401 | Unauthorized | No | Check API credentials | | 403 | Forbidden | No | API key lacks permission for this endpoint | | 404 | Not Found | No | Check endpoint URL or resource ID | | 408 | Request Timeout | Yes | Retry with backoff | | 422 | Unprocessable Entity | No | Validation error — check required fields | | 429 | Rate Limited | Yes | Back off, respect Retry-After header | | 500 | Internal Server Error | Yes | Retry with exponential backoff | | 503 | Service Unavailable | Yes | Check status.customer.io, retry later |
typescript// WRONG — mixing up API key types import { TrackClient, APIClient, RegionUS } from "customerio-node"; // Track API uses Site ID + Track API Key (Basic Auth) const cio = new TrackClient(siteId, trackApiKey, { region: RegionUS }); // App API uses App API Key (Bearer Auth) — DIFFERENT key const api = new APIClient(appApiKey, { region: RegionUS }); // Common mistake: using Track API key for App API client // const api = new APIClient(trackApiKey); // WRONG — will get 401
Fix: Verify you're using the right key type. Track API credentials are under "Tracking API Key" in Settings. App API key is under "App API Key" — it's a separate bearer token.
typescript// WRONG — Customer.io expects Unix seconds, not milliseconds await cio.identify("user-1", { created_at: Date.now(), // 1704067200000 — TOO LARGE }); // CORRECT — divide by 1000 await cio.identify("user-1", { created_at: Math.floor(Date.now() / 1000), // 1704067200 });
Customer.io silently accepts millisecond timestamps but interprets them as dates thousands of years in the future, breaking segment conditions and campaign triggers.
typescript// WRONG — event name doesn't match dashboard await cio.track("user-1", { name: "SignedUp", data: {} }); // Dashboard expects: "signed_up" (case-sensitive) // CORRECT — exact match, snake_case await cio.track("user-1", { name: "signed_up", data: {} });
Checklist:
identify() first)typescriptimport { APIClient, SendEmailRequest, RegionUS } from "customerio-node"; const api = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, { region: RegionUS, }); // Common 422 errors: // 1. Wrong transactional_message_id const request = new SendEmailRequest({ to: "user@example.com", transactional_message_id: "999", // Must exist in dashboard message_data: { name: "Jane" }, identifiers: { id: "user-123" }, }); // 2. Missing required message_data fields // If template uses {{ data.reset_url }}, message_data must include reset_url // 3. Invalid email address // "to" must be a valid email format
typescript// Implement backoff when you hit 429 async function withBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> { for (let i = 0; i <= maxRetries; i++) { try { return await fn(); } catch (err: any) { if (err.statusCode === 429 && i < maxRetries) { // Respect Retry-After header if present, otherwise exponential backoff const retryAfter = err.headers?.["retry-after"]; const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, i) * 1000 + Math.random() * 500; console.warn(`Rate limited. Retrying in ${delay}ms...`); await new Promise((r) => setTimeout(r, delay)); continue; } throw err; } } throw new Error("Max retries exceeded"); } // Usage await withBackoff(() => cio.identify("user-1", { email: "user@example.com" }));
typescript// WRONG — EU account hitting US endpoint const cio = new TrackClient(siteId, apiKey, { region: RegionUS }); // Returns 401 because credentials are for EU workspace // CORRECT import { RegionEU } from "customerio-node"; const cio = new TrackClient(siteId, apiKey, { region: RegionEU });
Diagnostic checklist:
email attribute? (Check People > user profile)bash# Test Track API authentication curl -s -w "\nHTTP %{http_code}\n" \ -u "$CUSTOMERIO_SITE_ID:$CUSTOMERIO_TRACK_API_KEY" \ -X PUT "https://track.customer.io/api/v1/customers/test-diag" \ -H "Content-Type: application/json" \ -d '{"email":"diag@example.com"}' # Test App API authentication curl -s -w "\nHTTP %{http_code}\n" \ -H "Authorization: Bearer $CUSTOMERIO_APP_API_KEY" \ "https://api.customer.io/v1/campaigns" # Check Customer.io status curl -s "https://status.customer.io/api/v2/status.json" | python3 -m json.tool
typescript// Centralized error handler for Customer.io operations async function safeCioCall<T>( operation: string, fn: () => Promise<T> ): Promise<T | null> { try { return await fn(); } catch (err: any) { const code = err.statusCode ?? err.status ?? "unknown"; console.error(`CIO ${operation} failed [${code}]:`, err.message); // Alert on auth errors — likely misconfiguration if (code === 401 || code === 403) { console.error("AUTH ERROR: Check API credentials immediately"); } // Don't crash the app for tracking failures return null; } } // Usage — never crashes your app await safeCioCall("identify", () => cio.identify("user-1", { email: "user@example.com" }) );
After resolving errors, proceed to customerio-debug-bundle for comprehensive debug reports.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→pass | 13,584 | 8,158 | -40% | 1 | 1 | 0% | 2,328 | 3,457 | +48% | 0 | 0 | — |
case-01 | fail→pass | 16,896 | 16,207 | -4% | 1 | 1 | 0% | 3,420 | 5,320 | +56% | 0 | 0 | — |
case-02 | pass→pass | 8,954 | 5,480 | -39% | 1 | 1 | 0% | 1,833 | 3,083 | +68% | 0 | 0 | — |
case-03 | pass→pass | 6,744 | 3,725 | -45% | 1 | 1 | 0% | 1,378 | 2,795 | +103% | 0 | 0 | — |
case-04 | pass→pass | 8,284 | 3,779 | -54% | 1 | 1 | 0% | 1,579 | 2,724 | +73% | 0 | 0 | — |
case-05 | fail→pass | 5,317 | 3,190 | -40% | 1 | 1 | 0% | 1,075 | 2,492 | +132% | 0 | 0 | — |
case-06 | pass→pass | 13,192 | 7,811 | -41% | 1 | 1 | 0% | 2,552 | 3,669 | +44% | 0 | 0 | — |
case-08 | fail→pass | 11,540 | 7,297 | -37% | 1 | 1 | 0% | 2,249 | 3,706 | +65% | 0 | 0 | — |
case-09 | pass→pass | 10,833 | 5,418 | -50% | 1 | 1 | 0% | 2,143 | 3,056 | +43% | 0 | 0 | — |
case-10 | pass→pass | 5,189 | 4,341 | -16% | 1 | 1 | 0% | 1,135 | 2,986 | +163% | 0 | 0 | — |
case-11 | fail→pass | 6,802 | 3,131 | -54% | 1 | 1 | 0% | 1,288 | 2,636 | +105% | 0 | 0 | — |
case-12 | pass→pass | 5,502 | 4,137 | -25% | 1 | 1 | 0% | 1,002 | 2,860 | +185% | 0 | 0 | — |
case-13 | pass→pass | 9,364 | 4,520 | -52% | 1 | 1 | 0% | 1,697 | 2,753 | +62% | 0 | 0 | — |
case-14 | pass→pass | 9,959 | 5,322 | -47% | 1 | 1 | 0% | 2,009 | 3,235 | +61% | 0 | 0 | — |
case-15 | pass→pass | 13,772 | 11,123 | -19% | 1 | 1 | 0% | 2,563 | 4,334 | +69% | 0 | 0 | — |
case-16 | fail→pass | 9,590 | 4,547 | -53% | 1 | 1 | 0% | 1,858 | 2,919 | +57% | 0 | 0 | — |
case-17 | pass→pass | 10,862 | 8,150 | -25% | 1 | 1 | 0% | 1,971 | 3,620 | +84% | 0 | 0 | — |
case-18 | pass→pass | 10,245 | 6,129 | -40% | 1 | 1 | 0% | 2,002 | 3,126 | +56% | 0 | 0 | — |
case-19 | pass→pass | 9,102 | 4,206 | -54% | 1 | 1 | 0% | 1,733 | 2,868 | +65% | 0 | 0 | — |
case-20 | pass→pass | 3,940 | 4,937 | +25% | 1 | 1 | 0% | 792 | 2,995 | +278% | 0 | 0 | — |
case-21 | pass→pass | 12,185 | 7,906 | -35% | 1 | 1 | 0% | 2,326 | 3,477 | +49% | 0 | 0 | — |
case-22 | pass→pass | 13,885 | 14,201 | +2% | 1 | 1 | 0% | 2,518 | 4,525 | +80% | 0 | 0 | — |
case-23 | pass→pass | 10,237 | 7,577 | -26% | 1 | 1 | 0% | 1,853 | 3,413 | +84% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +26 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.