jeremylongshore

customerio-cost-tuning

@jeremylongshore/customerio-cost-tuning
jeremylongshore
1,004
123 forks
Updated 1/18/2026
View on GitHub

Optimize Customer.io costs and usage. Use when reducing expenses, optimizing usage, or right-sizing your Customer.io plan. Trigger with phrases like "customer.io cost", "reduce customer.io spend", "customer.io billing", "customer.io pricing".

Installation

$skills install @jeremylongshore/customerio-cost-tuning
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Pathplugins/saas-packs/customerio-pack/skills/customerio-cost-tuning/SKILL.md
Branchmain
Scoped Name@jeremylongshore/customerio-cost-tuning

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

skills list

Skill Instructions


name: customerio-cost-tuning description: | Optimize Customer.io costs and usage. Use when reducing expenses, optimizing usage, or right-sizing your Customer.io plan. Trigger with phrases like "customer.io cost", "reduce customer.io spend", "customer.io billing", "customer.io pricing". allowed-tools: Read, Write, Edit, Bash(gh:), Bash(curl:) version: 1.0.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io

Customer.io Cost Tuning

Overview

Optimize Customer.io costs by managing profiles, reducing unnecessary operations, and right-sizing your usage.

Prerequisites

  • Access to Customer.io billing dashboard
  • Understanding of pricing model
  • API access for usage analysis

Customer.io Pricing Model

ComponentPricing Basis
ProfilesNumber of people tracked
EmailsVolume sent (included amount varies)
SMSPer message sent
PushVolume sent
ObjectsIncluded with plan

Instructions

Step 1: Profile Cleanup

// scripts/profile-audit.ts
import { APIClient, RegionUS } from '@customerio/track';

interface ProfileAudit {
  total: number;
  inactive30Days: number;
  inactive90Days: number;
  noEmail: number;
  suppressed: number;
  recommendations: string[];
}

async function auditProfiles(): Promise<ProfileAudit> {
  const audit: ProfileAudit = {
    total: 0,
    inactive30Days: 0,
    inactive90Days: 0,
    noEmail: 0,
    suppressed: 0,
    recommendations: []
  };

  // Query via Customer.io App API or export
  // Analyze profile data

  const now = Math.floor(Date.now() / 1000);
  const thirtyDaysAgo = now - (30 * 24 * 60 * 60);
  const ninetyDaysAgo = now - (90 * 24 * 60 * 60);

  // Example analysis
  if (audit.inactive90Days > audit.total * 0.3) {
    audit.recommendations.push(
      'Consider archiving profiles inactive >90 days to reduce costs'
    );
  }

  if (audit.noEmail > audit.total * 0.1) {
    audit.recommendations.push(
      'Remove profiles without email addresses (cannot receive communications)'
    );
  }

  return audit;
}

Step 2: Suppress Inactive Users

// lib/profile-management.ts
import { TrackClient, RegionUS } from '@customerio/track';

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

// Suppress users who haven't engaged
async function suppressInactiveUsers(
  userIds: string[],
  dryRun: boolean = true
): Promise<{ suppressed: string[]; errors: string[] }> {
  const results = { suppressed: [] as string[], errors: [] as string[] };

  for (const userId of userIds) {
    if (dryRun) {
      console.log(`[DRY RUN] Would suppress: ${userId}`);
      results.suppressed.push(userId);
      continue;
    }

    try {
      await client.suppress(userId);
      results.suppressed.push(userId);
    } catch (error: any) {
      results.errors.push(`${userId}: ${error.message}`);
    }
  }

  return results;
}

// Delete users to fully remove from billing
async function deleteUsers(
  userIds: string[],
  dryRun: boolean = true
): Promise<{ deleted: string[]; errors: string[] }> {
  const results = { deleted: [] as string[], errors: [] as string[] };

  for (const userId of userIds) {
    if (dryRun) {
      console.log(`[DRY RUN] Would delete: ${userId}`);
      results.deleted.push(userId);
      continue;
    }

    try {
      await client.destroy(userId);
      results.deleted.push(userId);
    } catch (error: any) {
      results.errors.push(`${userId}: ${error.message}`);
    }
  }

  return results;
}

Step 3: Event Deduplication

// lib/smart-tracking.ts
import { LRUCache } from 'lru-cache';
import { TrackClient } from '@customerio/track';

const recentEvents = new LRUCache<string, number>({
  max: 100000,
  ttl: 3600000 // 1 hour
});

interface TrackingConfig {
  dedupWindowMs: number;
  skipEvents: string[];
  sampleRate: Record<string, number>;
}

const config: TrackingConfig = {
  dedupWindowMs: 60000, // 1 minute dedup window
  skipEvents: [
    'page_viewed', // High volume, low value
    'heartbeat'
  ],
  sampleRate: {
    'feature_used': 0.1, // Sample 10% of feature usage
    'search_performed': 0.5 // Sample 50% of searches
  }
};

export function shouldTrackEvent(
  userId: string,
  eventName: string,
  data?: Record<string, any>
): boolean {
  // Skip excluded events
  if (config.skipEvents.includes(eventName)) {
    return false;
  }

  // Apply sampling for high-volume events
  const sampleRate = config.sampleRate[eventName];
  if (sampleRate !== undefined && Math.random() > sampleRate) {
    return false;
  }

  // Deduplicate identical events
  const eventKey = `${userId}:${eventName}:${JSON.stringify(data || {})}`;
  if (recentEvents.has(eventKey)) {
    return false;
  }

  recentEvents.set(eventKey, Date.now());
  return true;
}

Step 4: Email Cost Optimization

// lib/email-optimization.ts
interface EmailOptimizationConfig {
  // Skip transactional emails for users who never open
  skipInactiveAfterDays: number;
  // Consolidate multiple notifications
  batchNotifications: boolean;
  batchWindowMinutes: number;
  // Suppress bounced emails
  suppressAfterBounces: number;
}

const emailConfig: EmailOptimizationConfig = {
  skipInactiveAfterDays: 180, // Skip users inactive 6 months
  batchNotifications: true,
  batchWindowMinutes: 30,
  suppressAfterBounces: 3
};

// Check if user should receive emails
async function shouldSendEmail(
  userId: string,
  emailType: 'transactional' | 'marketing'
): Promise<boolean> {
  // Always send critical transactional (password reset, security)
  if (emailType === 'transactional') {
    return true;
  }

  // Check engagement history
  const user = await getUserMetrics(userId);

  // Skip users who haven't opened in 6 months
  const sixMonthsAgo = Date.now() - (180 * 24 * 60 * 60 * 1000);
  if (user.lastEmailOpenedAt < sixMonthsAgo) {
    return false;
  }

  // Skip users with high bounce count
  if (user.bounceCount >= emailConfig.suppressAfterBounces) {
    return false;
  }

  return true;
}

Step 5: Usage Monitoring Dashboard

// lib/usage-monitor.ts
interface UsageMetrics {
  period: string;
  profiles: {
    total: number;
    new: number;
    deleted: number;
  };
  events: {
    total: number;
    byType: Record<string, number>;
  };
  emails: {
    sent: number;
    delivered: number;
    opened: number;
    bounced: number;
  };
  estimatedCost: number;
}

async function getUsageMetrics(
  startDate: Date,
  endDate: Date
): Promise<UsageMetrics> {
  // Query Customer.io Reporting API
  // or aggregate from your tracking

  return {
    period: `${startDate.toISOString()} - ${endDate.toISOString()}`,
    profiles: {
      total: 10000,
      new: 500,
      deleted: 100
    },
    events: {
      total: 50000,
      byType: {
        'signed_up': 500,
        'feature_used': 20000,
        'page_viewed': 25000 // Candidate for sampling
      }
    },
    emails: {
      sent: 15000,
      delivered: 14500,
      opened: 4350,
      bounced: 150
    },
    estimatedCost: 299 // Monthly estimate
  };
}

// Alert on unexpected usage spikes
function checkUsageAlerts(metrics: UsageMetrics): string[] {
  const alerts: string[] = [];

  // Profile growth alert
  if (metrics.profiles.new > metrics.profiles.total * 0.1) {
    alerts.push('Unusual profile growth detected');
  }

  // Event volume alert
  if (metrics.events.total > 100000) {
    alerts.push('High event volume - consider sampling');
  }

  // Bounce rate alert
  if (metrics.emails.bounced / metrics.emails.sent > 0.05) {
    alerts.push('High bounce rate - clean email list');
  }

  return alerts;
}

Step 6: Cost Reduction Checklist

## Monthly Cost Review Checklist

### Profile Optimization
- [ ] Remove profiles with no email
- [ ] Archive inactive profiles (>90 days)
- [ ] Suppress hard bounced emails
- [ ] Merge duplicate profiles

### Event Optimization
- [ ] Identify high-volume, low-value events
- [ ] Implement sampling for analytics events
- [ ] Deduplicate redundant events
- [ ] Remove deprecated event types

### Email Optimization
- [ ] Clean suppression list
- [ ] Re-engage or remove inactive subscribers
- [ ] Consolidate notification emails
- [ ] Optimize send frequency

### Plan Optimization
- [ ] Review plan vs actual usage
- [ ] Consider annual billing for discount
- [ ] Evaluate feature usage vs plan tier

Cost Savings Estimates

OptimizationTypical Savings
Profile cleanup10-30%
Event deduplication5-15%
Email list hygiene5-10%
Sampling high-volume events10-20%
Annual billing10-20%

Error Handling

IssueSolution
Accidental deletionCustomer.io has 30-day recovery
Over-suppressionTrack suppression reasons
Usage spikeSet up usage alerts

Resources

Next Steps

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

More by jeremylongshore

View all
rabbitmq-queue-setup
1,004

Rabbitmq Queue Setup - Auto-activating skill for Backend Development. Triggers on: rabbitmq queue setup, rabbitmq queue setup Part of the Backend Development skill category.

model-evaluation-suite
1,004

evaluating-machine-learning-models: This skill allows Claude to evaluate machine learning models using a comprehensive suite of metrics. It should be used when the user requests model performance analysis, validation, or testing. Claude can use this skill to assess model accuracy, precision, recall, F1-score, and other relevant metrics. Trigger this skill when the user mentions "evaluate model", "model performance", "testing metrics", "validation results", or requests a comprehensive "model evaluation".

neural-network-builder
1,004

building-neural-networks: This skill allows Claude to construct and configure neural network architectures using the neural-network-builder plugin. It should be used when the user requests the creation of a new neural network, modification of an existing one, or assistance with defining the layers, parameters, and training process. The skill is triggered by requests involving terms like "build a neural network," "define network architecture," "configure layers," or specific mentions of neural network types (e.g., "CNN," "RNN," "transformer").

oauth-callback-handler
1,004

Oauth Callback Handler - Auto-activating skill for API Integration. Triggers on: oauth callback handler, oauth callback handler Part of the API Integration skill category.