CU

customerio-cost-tuning

Reduce Customer.io expenses by cleaning inactive users and optimizing event tracking.

Install

mkdir -p .claude/skills/customerio-cost-tuning && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/9119" && unzip -o skill.zip -d .claude/skills/customerio-cost-tuning && rm skill.zip

Installs to .claude/skills/customerio-cost-tuning

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Optimize Customer.io costs and usage efficiency.
48 charsno explicit “when” trigger
Intermediate

Key capabilities

  • Audit profile counts for billing optimization
  • Suppress and delete inactive user profiles
  • Deduplicate event tracking calls
  • Monitor API usage trends

How it works

The skill identifies cost drivers like profile counts and event volume, providing scripts to suppress inactive users and deduplicate events to reduce billing impact.

Inputs & outputs

You give it
Customer.io site ID and API key
You get back
Optimized profile list and usage metrics

When to use customerio-cost-tuning

  • Reduce Customer.io bill
  • Audit profile counts
  • Clean inactive user data
  • Optimize event tracking costs

About this skill

Customer.io Cost Tuning

Overview

Optimize Customer.io costs by managing profile count (the primary billing driver), suppressing/deleting inactive users, deduplicating events, reducing unnecessary API calls, and monitoring usage trends.

How Customer.io Pricing Works

Customer.io bills based on profile count (number of identified people in your workspace) and email/SMS volume. Key cost drivers:

FactorImpactOptimization Strategy
Total profilesPrimary cost driverDelete inactive profiles
Email sendsPer-email cost above tierSuppress unengaged users
SMS sendsPer-SMS costOnly send to opt-in users
OveridentificationCreates unnecessary profilesDon't identify users who'll never receive messages
Event volumeCan increase processing costsDeduplicate and sample

Instructions

Step 1: Profile Audit

// scripts/cio-profile-audit.ts
// Audit your Customer.io integration for cost optimization opportunities

import { TrackClient, RegionUS } from "customerio-node";

const cio = new TrackClient(
  process.env.CUSTOMERIO_SITE_ID!,
  process.env.CUSTOMERIO_TRACK_API_KEY!,
  { region: RegionUS }
);

// Check: Are you identifying users who'll never receive messages?
const AUDIT_RULES = {
  // Users without email can't receive email campaigns
  noEmail: "Don't identify users without email unless using push/SMS",

  // Test users should be cleaned up
  testUsers: "Suppress and delete test-*, ci-*, dev-* prefixed users",

  // Anonymous users that never convert inflate profile count
  staleAnonymous: "Delete anonymous profiles older than 90 days without conversion",

  // Inactive users who haven't opened email in 6+ months
  unengaged: "Suppress users with no email opens in 180+ days",
};

console.log("=== Customer.io Cost Audit Rules ===\n");
for (const [rule, action] of Object.entries(AUDIT_RULES)) {
  console.log(`${rule}: ${action}`);
}
console.log("\nRun these checks in Customer.io dashboard:");
console.log("1. People > Segments > Create 'Inactive 90 days' segment");
console.log("2. People > Segments > Create 'No email attribute' segment");
console.log("3. People > Filter by created_at < 90 days ago AND email_opened = 0");

Step 2: Suppress and Delete Inactive Users

// scripts/cio-cleanup-inactive.ts
import { TrackClient, RegionUS } from "customerio-node";

const cio = new TrackClient(
  process.env.CUSTOMERIO_SITE_ID!,
  process.env.CUSTOMERIO_TRACK_API_KEY!,
  { region: RegionUS }
);

interface CleanupTarget {
  userId: string;
  reason: string;
}

async function cleanupInactiveUsers(
  targets: CleanupTarget[],
  dryRun: boolean = true
): Promise<void> {
  let suppressed = 0;
  let deleted = 0;
  let errors = 0;

  for (const target of targets) {
    if (dryRun) {
      console.log(`[DRY RUN] Would suppress+delete: ${target.userId} (${target.reason})`);
      continue;
    }

    try {
      // Step 1: Suppress — stops all messaging immediately
      await cio.suppress(target.userId);
      suppressed++;

      // Step 2: Destroy — removes from billing
      await cio.destroy(target.userId);
      deleted++;

      // Rate limit to 50/sec for bulk operations
      await new Promise((r) => setTimeout(r, 20));
    } catch (err: any) {
      errors++;
      console.error(`Failed ${target.userId}: ${err.message}`);
    }

    if ((suppressed + errors) % 100 === 0) {
      console.log(`Progress: ${suppressed} deleted, ${errors} errors`);
    }
  }

  console.log(`\nResult: ${suppressed} suppressed, ${deleted} deleted, ${errors} errors`);
}

// Usage: Build target list from your database
// const inactiveUsers = await db.query(`
//   SELECT id FROM users
//   WHERE last_login_at < NOW() - INTERVAL '180 days'
//   AND email_verified = false
// `);

Step 3: Event Deduplication

// lib/customerio-dedup-events.ts
// Prevent sending duplicate events that inflate volume

import { createHash } from "crypto";
import { TrackClient, RegionUS } from "customerio-node";

const cio = new TrackClient(
  process.env.CUSTOMERIO_SITE_ID!,
  process.env.CUSTOMERIO_TRACK_API_KEY!,
  { region: RegionUS }
);

// Simple LRU dedup (use Redis in production)
const recentEvents = new Map<string, number>();
const MAX_CACHE = 50_000;
const DEDUP_WINDOW_MS = 60 * 1000;  // 1 minute window

function isDuplicate(userId: string, eventName: string, data?: any): boolean {
  const hash = createHash("sha256")
    .update(`${userId}:${eventName}:${JSON.stringify(data ?? {})}`)
    .digest("hex")
    .substring(0, 12);

  const last = recentEvents.get(hash);
  if (last && Date.now() - last < DEDUP_WINDOW_MS) {
    return true;
  }

  recentEvents.set(hash, Date.now());

  // Prevent unbounded growth
  if (recentEvents.size > MAX_CACHE) {
    const cutoff = Date.now() - DEDUP_WINDOW_MS;
    for (const [key, time] of recentEvents) {
      if (time < cutoff) recentEvents.delete(key);
    }
  }

  return false;
}

export async function trackDeduped(
  userId: string,
  name: string,
  data?: Record<string, any>
): Promise<void> {
  if (isDuplicate(userId, name, data)) {
    return; // Skip duplicate
  }
  await cio.track(userId, { name, data });
}

Step 4: Event Sampling for High-Volume Events

// lib/customerio-sampling.ts
// Sample high-volume events to reduce API calls

const EVENT_SAMPLE_RATES: Record<string, number> = {
  page_viewed: 0.1,          // Sample 10% of page views
  button_clicked: 0.25,      // Sample 25% of clicks
  search_performed: 0.5,     // Sample 50% of searches
  signed_up: 1.0,            // Always track signups
  checkout_completed: 1.0,   // Always track purchases
  subscription_cancelled: 1.0, // Always track cancellations
};

export function shouldTrack(eventName: string): boolean {
  const rate = EVENT_SAMPLE_RATES[eventName] ?? 1.0;
  return Math.random() < rate;
}

// Usage
if (shouldTrack("page_viewed")) {
  await cio.track(userId, {
    name: "page_viewed",
    data: { url: "/pricing", sampled: true },
  });
}

Step 5: Usage Monitoring

// scripts/cio-usage-monitor.ts
// Track your Customer.io usage trends

interface UsageMetrics {
  identifyCalls: number;
  trackCalls: number;
  transactionalSends: number;
  broadcastTriggers: number;
  webhooksReceived: number;
}

class UsageMonitor {
  private metrics: UsageMetrics = {
    identifyCalls: 0,
    trackCalls: 0,
    transactionalSends: 0,
    broadcastTriggers: 0,
    webhooksReceived: 0,
  };

  increment(metric: keyof UsageMetrics): void {
    this.metrics[metric]++;
  }

  report(): void {
    console.log("\n=== Customer.io Usage Report ===");
    console.log(`Period: ${new Date().toISOString()}`);
    for (const [key, value] of Object.entries(this.metrics)) {
      console.log(`  ${key}: ${value.toLocaleString()}`);
    }
    const total = Object.values(this.metrics).reduce((a, b) => a + b, 0);
    console.log(`  TOTAL API calls: ${total.toLocaleString()}`);
  }

  reset(): void {
    for (const key of Object.keys(this.metrics)) {
      this.metrics[key as keyof UsageMetrics] = 0;
    }
  }
}

export const usageMonitor = new UsageMonitor();

Cost Savings Estimates

OptimizationTypical SavingsImplementation Effort
Delete inactive profiles (180+ days)15-30% profile costLow
Event deduplication5-15% event volumeLow
Event sampling (analytics events)50-80% event volume for sampled eventsLow
Suppress bounced emails2-5% email costLow
Don't identify email-less users5-20% profile costMedium
Annual billing10-20% total costNone

Monthly Cost Review Checklist

  • Review profile count trend (People > Overview)
  • Identify and delete stale test profiles
  • Review segment for users with no email attribute
  • Check bounce rate and suppress chronic bouncers
  • Review event volume by type (optimize high-volume/low-value events)
  • Compare plan tier vs actual usage

Resources

Next Steps

After cost optimization, proceed to customerio-reference-architecture for enterprise patterns.

When not to use it

  • When active user data must be preserved for critical campaigns
  • When event sampling impacts analytics accuracy

Prerequisites

Customer.io API credentials

Limitations

  • Requires manual audit of user segments
  • Event sampling reduces data granularity

How it compares

This approach targets specific billing drivers like profile counts rather than general API usage.

Compared to similar skills

customerio-cost-tuning side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
customerio-cost-tuning (this skill)027dReviewIntermediate
session-status17moReviewBeginner
it-operations17moNo flagsAdvanced
perplexity-incident-runbook027dReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

Search skills

Search the agent skills registry