Claude Code Plugins

Community-maintained marketplace

Feedback

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name customerio-known-pitfalls
description Identify and avoid Customer.io anti-patterns. Use when reviewing integrations, avoiding common mistakes, or optimizing existing Customer.io implementations. Trigger with phrases like "customer.io mistakes", "customer.io anti-patterns", "customer.io best practices", "customer.io gotchas".
allowed-tools Read, Write, Edit, Bash(kubectl:*), Bash(curl:*)
version 1.0.0
license MIT
author Jeremy Longshore <jeremy@intentsolutions.io>

Customer.io Known Pitfalls

Overview

Avoid common mistakes and anti-patterns when integrating with Customer.io.

Pitfall Categories

1. Authentication & Setup

Pitfall: Using App API key for Track API

// WRONG: Using App API key for tracking
const client = new TrackClient(siteId, appApiKey); // Will fail!

// CORRECT: Use Track API key for tracking
const client = new TrackClient(siteId, trackApiKey);

// Use App API key only for transactional and reporting APIs
const apiClient = new APIClient(appApiKey);

Pitfall: Millisecond timestamps

// WRONG: JavaScript milliseconds
{ created_at: Date.now() } // 1704067200000 - will be rejected!

// CORRECT: Unix seconds
{ created_at: Math.floor(Date.now() / 1000) } // 1704067200

Pitfall: Hardcoded credentials

// WRONG: Credentials in code
const client = new TrackClient('abc123', 'secret-key'); // Security risk!

// CORRECT: Environment variables
const client = new TrackClient(
  process.env.CUSTOMERIO_SITE_ID!,
  process.env.CUSTOMERIO_API_KEY!
);

2. User Identification

Pitfall: Tracking events before identify

// WRONG: Track before identify
await client.track(userId, { name: 'signup' }); // User doesn't exist!
await client.identify(userId, { email: 'user@example.com' });

// CORRECT: Always identify first
await client.identify(userId, { email: 'user@example.com' });
await client.track(userId, { name: 'signup' });

Pitfall: Changing user IDs

// WRONG: User ID changes when email changes
const userId = user.email; // Changing email = new user!

// CORRECT: Use immutable identifier
const userId = user.databaseId; // UUIDs or auto-increment IDs

Pitfall: Anonymous ID not merged

// WRONG: No anonymous_id linking
await client.identify(newUserId, { email: 'user@example.com' });
// Anonymous activity is orphaned!

// CORRECT: Include anonymous_id for merging
await client.identify(newUserId, {
  email: 'user@example.com',
  anonymous_id: previousAnonymousId
});

3. Event Tracking

Pitfall: Inconsistent event names

// WRONG: Inconsistent casing and naming
await client.track(userId, { name: 'UserSignedUp' });
await client.track(userId, { name: 'user-signed-up' });
await client.track(userId, { name: 'user_signedup' });

// CORRECT: Consistent snake_case
await client.track(userId, { name: 'user_signed_up' });

Pitfall: Too many unique events

// WRONG: Dynamic event names create clutter
await client.track(userId, { name: `viewed_product_${productId}` });
// Creates thousands of unique events!

// CORRECT: Use properties for variations
await client.track(userId, {
  name: 'product_viewed',
  data: { product_id: productId }
});

Pitfall: Blocking on analytics

// WRONG: Waiting for analytics in request path
app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await client.identify(user.id, { email: user.email }); // Blocks!
  res.json({ user });
});

// CORRECT: Fire-and-forget
app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  client.identify(user.id, { email: user.email })
    .catch(err => console.error('Customer.io error:', err));
  res.json({ user });
});

4. Data Quality

Pitfall: Missing required attributes

// WRONG: No email attribute
await client.identify(userId, { name: 'John' });
// User can't receive emails!

// CORRECT: Always include email for email campaigns
await client.identify(userId, {
  email: 'john@example.com',
  name: 'John'
});

Pitfall: Inconsistent attribute types

// WRONG: Sometimes string, sometimes number
await client.identify(userId1, { plan: 'premium' });
await client.identify(userId2, { plan: 1 });

// CORRECT: Consistent types
await client.identify(userId, { plan: 'premium' });

Pitfall: PII in segment names or event names

// WRONG: PII exposed
await client.track(userId, { name: `email_${user.email}` });
// Creates segment: "email_john@example.com"

// CORRECT: Use attributes, not names
await client.track(userId, {
  name: 'email_action',
  data: { email: user.email }
});

5. Campaign Configuration

Pitfall: No unsubscribe handling

## WRONG: No unsubscribe link
Email template without {{{ unsubscribe_url }}}

## CORRECT: Always include unsubscribe
<a href="{{{ unsubscribe_url }}}">Unsubscribe</a>

Pitfall: Trigger on every attribute update

# WRONG: Trigger fires on every identify
trigger:
  event: "identify"

# CORRECT: Trigger on specific events
trigger:
  event: "signed_up"

6. Delivery Issues

Pitfall: Ignoring bounces

// WRONG: No bounce handling
webhooks.on('email_bounced', () => {
  // Do nothing
});

// CORRECT: Suppress or update on bounce
webhooks.on('email_bounced', async (event) => {
  await client.suppress(event.data.customer_id);
  // Or mark email as invalid in your database
});

Pitfall: Not monitoring complaint rate

// WRONG: Ignoring spam complaints
// Leads to deliverability issues!

// CORRECT: Alert on complaints
webhooks.on('email_complained', async (event) => {
  // Immediately suppress
  await client.suppress(event.data.customer_id);
  // Alert the team
  await alertTeam(`Spam complaint from ${event.data.email_address}`);
});

7. Performance Issues

Pitfall: No connection pooling

// WRONG: New client per request
app.get('/api', async (req, res) => {
  const client = new TrackClient(siteId, apiKey); // Creates new connection!
  await client.identify(userId, data);
});

// CORRECT: Reuse client
const client = new TrackClient(siteId, apiKey);
app.get('/api', async (req, res) => {
  await client.identify(userId, data);
});

Pitfall: No rate limiting

// WRONG: Uncontrolled burst
for (const user of users) {
  await client.identify(user.id, user.data); // 10k requests instantly!
}

// CORRECT: Rate limited
const limiter = new Bottleneck({ maxConcurrent: 10, minTime: 10 });
for (const user of users) {
  await limiter.schedule(() => client.identify(user.id, user.data));
}

Anti-Pattern Detection Script

// scripts/audit-integration.ts
interface AuditResult {
  issues: string[];
  warnings: string[];
  score: number;
}

async function auditIntegration(): Promise<AuditResult> {
  const result: AuditResult = { issues: [], warnings: [], score: 100 };

  // Check for hardcoded credentials
  const files = await glob('**/*.{ts,js}');
  for (const file of files) {
    const content = await readFile(file, 'utf-8');
    if (content.includes('site_') && content.includes('api_')) {
      result.issues.push(`Possible hardcoded credentials in ${file}`);
      result.score -= 20;
    }
  }

  // Check for millisecond timestamps
  if (await hasPattern(/Date\.now\(\)(?!\s*\/\s*1000)/)) {
    result.warnings.push('Possible millisecond timestamps detected');
    result.score -= 5;
  }

  // Check for track before identify pattern
  if (await hasPattern(/track\([^)]+\)[\s\S]{0,500}identify\(/)) {
    result.issues.push('Track before identify pattern detected');
    result.score -= 15;
  }

  return result;
}

Quick Reference

Pitfall Fix
Wrong API key Track API for tracking, App API for transactional
Milliseconds Use Math.floor(Date.now() / 1000)
Track before identify Always identify first
Changing user IDs Use immutable database IDs
No email attribute Include email for email campaigns
Dynamic event names Use properties instead
Blocking requests Fire-and-forget pattern
No bounce handling Suppress on bounce
No rate limiting Use Bottleneck or similar

Resources

Conclusion

Following these guidelines will help you avoid common pitfalls and build a reliable Customer.io integration. Regularly audit your implementation against this checklist to catch issues early.