| 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
const client = new TrackClient(siteId, appApiKey);
const client = new TrackClient(siteId, trackApiKey);
const apiClient = new APIClient(appApiKey);
Pitfall: Millisecond timestamps
{ created_at: Date.now() }
{ created_at: Math.floor(Date.now() / 1000) }
Pitfall: Hardcoded credentials
const client = new TrackClient('abc123', 'secret-key');
const client = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_API_KEY!
);
2. User Identification
Pitfall: Tracking events before identify
await client.track(userId, { name: 'signup' });
await client.identify(userId, { email: 'user@example.com' });
await client.identify(userId, { email: 'user@example.com' });
await client.track(userId, { name: 'signup' });
Pitfall: Changing user IDs
const userId = user.email;
const userId = user.databaseId;
Pitfall: Anonymous ID not merged
await client.identify(newUserId, { email: 'user@example.com' });
await client.identify(newUserId, {
email: 'user@example.com',
anonymous_id: previousAnonymousId
});
3. Event Tracking
Pitfall: Inconsistent event names
await client.track(userId, { name: 'UserSignedUp' });
await client.track(userId, { name: 'user-signed-up' });
await client.track(userId, { name: 'user_signedup' });
await client.track(userId, { name: 'user_signed_up' });
Pitfall: Too many unique events
await client.track(userId, { name: `viewed_product_${productId}` });
await client.track(userId, {
name: 'product_viewed',
data: { product_id: productId }
});
Pitfall: Blocking on analytics
app.post('/signup', async (req, res) => {
const user = await createUser(req.body);
await client.identify(user.id, { email: user.email });
res.json({ user });
});
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
await client.identify(userId, { name: 'John' });
await client.identify(userId, {
email: 'john@example.com',
name: 'John'
});
Pitfall: Inconsistent attribute types
await client.identify(userId1, { plan: 'premium' });
await client.identify(userId2, { plan: 1 });
await client.identify(userId, { plan: 'premium' });
Pitfall: PII in segment names or event names
await client.track(userId, { name: `email_${user.email}` });
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
trigger:
event: "identify"
trigger:
event: "signed_up"
6. Delivery Issues
Pitfall: Ignoring bounces
webhooks.on('email_bounced', () => {
});
webhooks.on('email_bounced', async (event) => {
await client.suppress(event.data.customer_id);
});
Pitfall: Not monitoring complaint rate
webhooks.on('email_complained', async (event) => {
await client.suppress(event.data.customer_id);
await alertTeam(`Spam complaint from ${event.data.email_address}`);
});
7. Performance Issues
Pitfall: No connection pooling
app.get('/api', async (req, res) => {
const client = new TrackClient(siteId, apiKey);
await client.identify(userId, data);
});
const client = new TrackClient(siteId, apiKey);
app.get('/api', async (req, res) => {
await client.identify(userId, data);
});
Pitfall: No rate limiting
for (const user of users) {
await client.identify(user.id, user.data);
}
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
interface AuditResult {
issues: string[];
warnings: string[];
score: number;
}
async function auditIntegration(): Promise<AuditResult> {
const result: AuditResult = { issues: [], warnings: [], score: 100 };
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;
}
}
if (await hasPattern(/Date\.now\(\)(?!\s*\/\s*1000)/)) {
result.warnings.();
result. -= ;
}
( ()) {
result..();
result. -= ;
}
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.