| 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
| Component | Pricing Basis |
|---|
| Profiles | Number of people tracked |
| Emails | Volume sent (included amount varies) |
| SMS | Per message sent |
| Push | Volume sent |
| Objects | Included with plan |
Instructions
Step 1: Profile Cleanup
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: []
};
const now = Math.floor(Date.now() / 1000);
const thirtyDaysAgo = now - (30 * 24 * 60 * 60);
ninetyDaysAgo = now - ( * * * );
(audit. > audit. * ) {
audit..(
);
}
(audit. > audit. * ) {
audit..(
);
}
audit;
}
Step 2: Suppress Inactive Users
import { TrackClient, RegionUS } from '@customerio/track';
const client = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_API_KEY!,
{ region: RegionUS }
);
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..(userId);
} (: ) {
results..();
}
}
results;
}
(): <{ : []; : [] }> {
results = { : [] [], : [] [] };
( userId userIds) {
(dryRun) {
.();
results..(userId);
;
}
{
client.(userId);
results..(userId);
} (: ) {
results..();
}
}
results;
}
Step 3: Event Deduplication
import { LRUCache } from 'lru-cache';
import { TrackClient } from '@customerio/track';
const recentEvents = new LRUCache<string, number>({
max: 100000,
ttl: 3600000
});
interface TrackingConfig {
dedupWindowMs: number;
skipEvents: string[];
sampleRate: Record<string, number>;
}
const config: TrackingConfig = {
dedupWindowMs: 60000,
skipEvents: [
'page_viewed',
'heartbeat'
],
sampleRate: {
'feature_used': 0.1,
'search_performed': 0.5
}
};
export function shouldTrackEvent(
userId: string,
: ,
?: <, >
): {
(config..(eventName)) {
;
}
sampleRate = config.[eventName];
(sampleRate !== && .() > sampleRate) {
;
}
eventKey = ;
(recentEvents.(eventKey)) {
;
}
recentEvents.(eventKey, .());
;
}
Step 4: Email Cost Optimization
interface EmailOptimizationConfig {
skipInactiveAfterDays: number;
batchNotifications: boolean;
batchWindowMinutes: number;
suppressAfterBounces: number;
}
const emailConfig: EmailOptimizationConfig = {
skipInactiveAfterDays: 180,
batchNotifications: true,
batchWindowMinutes: 30,
suppressAfterBounces: 3
};
async function shouldSendEmail(
userId: string,
emailType: 'transactional' | 'marketing'
): Promise<boolean> {
if (emailType === 'transactional') {
return true;
}
const user = await getUserMetrics(userId);
sixMonthsAgo = .() - ( * * * * );
(user. < sixMonthsAgo) {
;
}
(user. >= emailConfig.) {
;
}
;
}
Step 5: Usage Monitoring Dashboard
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> {
return {
period: `${startDate.toISOString()} - ${endDate.toISOString()}`,
profiles: {
total: 10000,
new: 500,
deleted:
},
: {
: ,
: {
: ,
: ,
:
}
},
: {
: ,
: ,
: ,
:
},
:
};
}
(): [] {
: [] = [];
(metrics.. > metrics.. * ) {
alerts.();
}
(metrics.. > ) {
alerts.();
}
(metrics.. / metrics.. > ) {
alerts.();
}
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
| Optimization | Typical Savings |
|---|
| Profile cleanup | 10-30% |
| Event deduplication | 5-15% |
| Email list hygiene | 5-10% |
| Sampling high-volume events | 10-20% |
| Annual billing | 10-20% |
Error Handling
| Issue | Solution |
|---|
| Accidental deletion | Customer.io has 30-day recovery |
| Over-suppression | Track suppression reasons |
| Usage spike | Set up usage alerts |
Resources
Next Steps
After cost optimization, proceed to customerio-reference-architecture for architecture patterns.