customerio-pack
Complete Customer.io integration skill pack with 24 skills covering marketing automation, email campaigns, SMS, push notifications, and customer journeys. Flagship tier vendor pack.
Installation
Open Claude Code and run this command:
/plugin install customerio-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
> 24 production-grade Claude Code skills for Customer.io marketing automation (email, push, SMS, in-app messaging)
Build, debug, and scale Customer.io integrations with real customerio-node SDK code -- TrackClient for behavioral data, APIClient for transactional messages and broadcasts, webhook handling, and enterprise reliability patterns.
Skills (24) plugin-local skills
Apply Customer.
Customer.io Advanced Troubleshooting
Output
- A minimal redacted root-cause record with affected environment, correlation IDs, evidence, mitigation, owner, and follow-up action.
- A tested recovery that avoids duplicate delivery, lost consent state, or disclosure of recipient data.
Examples
When a production campaign appears delayed, compare aggregate queue/acceptance metrics with a synthetic control event, inspect the workflow/template version and opaque correlation ID, and apply the documented pause or rollback if customer impact is confirmed. Escalate with a redacted bundle; do not export recipient lists or replay the whole campaign until idempotency and consent are verified.
Overview
Advanced debugging techniques for complex Customer.io issues: systematic investigation framework, API debug client, user profile analysis, campaign/broadcast debugging, network diagnostics, and incident response runbooks.
Prerequisites
- Access to Customer.io dashboard (admin recommended)
- Application logs access
curlfor API testing
Troubleshooting Framework
For every issue, answer these five questions first:
- What is the expected vs actual behavior?
- When did the issue start? (Check deploy history, CIO status page)
- Who is affected — one user, a segment, or everyone?
- Where in the pipeline — API call, delivery, or rendering?
- How often — every time, intermittent, or one-time?
Instructions
Step 1: API Debug Client
// lib/customerio-debug.ts
import { TrackClient, APIClient, RegionUS } from "customerio-node";
export class DebugCioClient {
private track: TrackClient;
constructor() {
this.track = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
}
async debugIdentify(userId: string, attrs: Record<string, any>) {
console.log(`\n--- Debug: identify("${userId}") ---`);
console.log("Attributes:", JSON.stringify(attrs, null, 2));
const start = Date.now();
try {
await this.track.identify(userId, attrs);
const latency = Date.now() - start;
console.log(`Result: SUCCESS (${latency}ms)`);
return { success: true, latency };
} catch (err: any) {
const latency = Date.now() - start;
console.log(`Result: FAILED (${latency}ms)`);
console.log(`Status: ${err.statusCode}`);
console.log(`Message: ${err.message}`);
console.log(`Body: ${JSON.stringify(err.body ?? err.response)}`);
return { success: false, latency, statusCode: err.statusCode, message: err.message };
}
}
async debugTrack(userId: string, name: string, data?: any) {
coConfigure Customer.
Customer.io CI Integration
Output
- A credential-free pull-request lane for schema/template/unit checks and a trusted scoped lane for optional integration tests.
- A redacted CI receipt with validation results and a safe failure/retry procedure.
Examples
Run event-schema and template tests on every pull request using fixtures, then execute a single synthetic integration event only from a protected branch with a development workspace secret. If a live check fails, retain redacted status and back off; never expose workspace credentials to forked code or bypass protected checks.
Overview
Set up CI/CD pipelines for Customer.io integrations: GitHub Actions workflow with unit + integration tests, test fixtures with automatic cleanup, pre-commit hooks, and environment-specific credential management.
Prerequisites
- GitHub repository with Node.js project
- Separate Customer.io workspace for CI testing (do NOT use production)
- GitHub Actions secrets configured
Instructions
Step 1: GitHub Actions Workflow
# .github/workflows/customerio-tests.yml
name: Customer.io Integration Tests
on:
push:
paths:
- "lib/customerio-*.ts"
- "services/customerio-*.ts"
- "tests/customerio*"
pull_request:
paths:
- "lib/customerio-*.ts"
- "services/customerio-*.ts"
env:
CUSTOMERIO_SITE_ID: ${{ secrets.CIO_TEST_SITE_ID }}
CUSTOMERIO_TRACK_API_KEY: ${{ secrets.CIO_TEST_TRACK_API_KEY }}
CUSTOMERIO_APP_API_KEY: ${{ secrets.CIO_TEST_APP_API_KEY }}
CUSTOMERIO_REGION: us
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx vitest run tests/customerio --reporter=verbose
env:
CUSTOMERIO_DRY_RUN: "true" # Unit tests use mocks
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests # Only run if unit tests pass
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Validate credentials
run: |
if [ -z "$CUSTOMERIO_SITE_ID" ]; then
echo "::warning::CIO credentials not configured — skipping integration tests"
exit 0
fi
- name: Run integration tests
run: npx vitest run tests/customerio.integration --reporter=verbose
- name: Cleanup test users
if: always()
run: npx tsx scripts/cio-cleanup-test-users.ts
Step 2: Test Fixtures and Helpers
// tests/helpers/cio-test-utils.ts
import { TrackClient, RegionUS } from "customerio-node";Diagnose and fix Customer.
Customer.io Common Errors
Output
- A classified Customer.io integration failure with redacted request/response evidence and an accountable owner.
- A bounded remediation or escalation that preserves message integrity, consent, and rate controls.
Error Handling
| Condition | Safe response |
|---|---|
| Authentication or authorization fails | Verify the scoped secret reference and workspace/region; rotate/revoke through the secret manager if exposure is suspected. |
| Delivery event is missing | Check identifier, consent, suppression, and event ordering before replaying anything. |
| Rate limit or transient error occurs | Apply documented backoff and idempotency; do not flood retries or disable limits. |
| Payload contains sensitive attributes | Redact diagnostics and validate the data contract before retrying. |
Examples
For a failed event, record its opaque correlation ID, status class, and destination environment. Confirm the profile ID, consent state, and idempotency key, then replay one sanitized test event only after the underlying failure is resolved.
Overview
Diagnose and fix the most frequent Customer.io integration errors: API status codes, SDK exceptions, delivery failures, campaign trigger issues, and transactional message problems.
Prerequisites
- Access to Customer.io dashboard
- API credentials configured
- Access to application logs
HTTP Status Code Reference
| Code | Meaning | Retryable | Action |
|---|---|---|---|
200 |
Success | N/A | No action needed |
400 |
Bad Request | No | Fix request payload — see details below |
401 |
Unauthorized | No | Check API credentials |
403 |
Forbidden | No | API key lacks permission for this endpoint |
404 |
Not Found | No | Check endpoint URL or resource ID |
408 |
Request Timeout | Yes | Retry with backoff |
422 |
Unprocessable Entity | No | Validation error — check required fields |
429 |
Rate Limited | Yes | Back off, respect Retry-After header |
500 |
Internal Server Error | Yes | Retry with exponential backoff |
503 |
Service Unavailable | Yes | Check status.customer.io, retry later |
Instructions
Error 1: Authen
Implement Customer.
Customer.io Core Features
Output
- A scoped, consent-aware Customer.io feature configuration with a validated data contract and owner.
- A test receipt showing intended audience, environment, event/message behavior, and safe rollback path.
Examples
Configure a feature in development with a synthetic profile and a narrowly defined event. Verify attributes, segment membership, and message preview before promotion. If the audience or consent behavior differs from expectation, disable the development configuration and correct the contract before proceeding.
Overview
Implement Customer.io's key platform features: transactional emails/push (password resets, receipts), API-triggered broadcasts (one-to-many on demand), segment-driving attributes, anonymous-to-known user merging, and person suppression/deletion.
Prerequisites
customerio-nodeinstalled- Track API credentials (
CUSTOMERIO_SITE_ID+CUSTOMERIO_TRACK_API_KEY) - App API credential (
CUSTOMERIO_APP_API_KEY) — required for transactional + broadcasts
Instructions
Feature 1: Transactional Email
Transactional messages are opt-in-implied messages (receipts, password resets). Create the template in Customer.io dashboard first, then call the API with data.
// lib/customerio-transactional.ts
import { APIClient, SendEmailRequest, RegionUS } from "customerio-node";
const api = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, {
region: RegionUS,
});
// Send a transactional email
// transactional_message_id comes from the Customer.io dashboard template
async function sendPasswordReset(email: string, userId: string, resetUrl: string) {
const request = new SendEmailRequest({
to: email,
transactional_message_id: "3", // Template ID from dashboard
message_data: {
reset_url: resetUrl,
expiry_hours: 24,
support_email: "help@yourapp.com",
},
identifiers: { id: userId }, // Links delivery metrics to user profile
});
const response = await api.sendEmail(request);
// response = { delivery_id: "abc123", queued_at: 1704067200 }
return response;
}
// Send an order confirmation with complex data
async function sendOrderConfirmation(
email: string,
userId: string,
order: { id: string; items: { name: string; qty: number; price: number }[]; total: number }
) {
const request = new SendEmailRequest({
to: email,
transactional_message_id: "5",
message_data: {
order_id: order.id,
items: order.items, // Accessible as {{ event.items }} in Liquid
total: order.total,
order_date: new Date().toISOString(),
},
identifiers: { id: userId },
});
return api.sendEmail(request);
}
Dashboard setup: Create tra
Optimize Customer.
Customer.io Cost Tuning
Prerequisites
- An approved cost owner, billing/usage baseline, message/event retention policy, and delivery SLO.
- Aggregate reporting that does not require exporting customer payloads or individual message content.
Output
- A measured cost optimization with owner, delivery/consent guardrails, and rollback threshold.
- An aggregate usage receipt supporting the decision without exposing recipient data.
Error Handling
| Condition | Safe response |
|---|---|
| Spend rises unexpectedly | Verify configuration, rate, and authorized campaigns; pause unsafe scale changes and investigate aggregate signals. |
| Optimization risks delivery or consent | Do not apply it until the service/data owner approves a safe test. |
| Data is missing or misallocated | Repair attribution before making a budget decision. |
Examples
Compare aggregate event volume and message spend for a development/staging campaign, reduce unnecessary duplicate events through an idempotency fix, and observe the agreed window. Revert if delivery errors, latency, or consent checks regress; do not suppress customer messages solely to make a dashboard look cheaper.
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:
| Factor | Impact | Optimization Strategy |
|---|---|---|
| Total profiles | Primary cost driver | Delete inactive profiles |
| Email sends | Per-email cost above tier | Suppress unengaged users |
| SMS sends | Per-SMS cost | Only send to opt-in users |
| Overidentification | Creates unnecessary profiles | Don't identify users who'll never receive messages |
| Event volume | Can increase processing costs | Deduplicate 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&Collect Customer.
Customer.io Debug Bundle
Output
- A minimal redacted diagnostic bundle containing correlation IDs, environment, timestamps, configuration version, and status/error class.
- An incident disposition that protects recipient data and credentials while allowing reproduction or vendor escalation.
Examples
For a delivery failure, collect the opaque event/message correlation ID, workspace, timestamp, template/workflow version, and redacted status. Do not export email addresses, full payloads, tokens, or message content. Reproduce with a synthetic profile, then share only the approved redacted bundle with support.
Current State
!node --version 2>/dev/null || echo 'Node.js: not installed' !npm list customerio-node 2>/dev/null | grep customerio || echo 'customerio-node: not installed'
Overview
Collect a comprehensive debug bundle for Customer.io support tickets: API connectivity tests, user profile inspection, SDK version info, environment validation, and a structured support report.
Prerequisites
- Customer.io API credentials configured
curlavailable for API tests- User ID or email of the affected user/delivery
Instructions
Step 1: API Connectivity Diagnostic
#!/usr/bin/env bash
set -euo pipefail
echo "=== Customer.io Debug Bundle ==="
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""
# 1. Check Customer.io status
echo "--- Platform Status ---"
curl -s "https://status.customer.io/api/v2/status.json" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Status: {d[\"status\"][\"description\"]}')" \
2>/dev/null || echo "Could not reach status page"
# 2. Test Track API authentication
echo ""
echo "--- Track API Auth ---"
TRACK_RESULT=$(curl -s -o /dev/null -w "%{http_code}" \
-u "${CUSTOMERIO_SITE_ID}:${CUSTOMERIO_TRACK_API_KEY}" \
-X PUT "https://track.customer.io/api/v1/customers/debug-test-$(date +%s)" \
-H "Content-Type: application/json" \
-d '{"email":"debug-test@example.com"}')
echo "Track API: HTTP ${TRACK_RESULT}"
# 3. Test App API authentication
echo ""
echo "--- App API Auth ---"
APP_RESULT=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${CUSTOMERIO_APP_API_KEY}" \
"https://api.customer.io/v1/campaigns")
echo "App API: HTTP ${APP_RESULT}"
# 4. DNS and latency
echo ""
echo "--- Network Diagnostics ---"
for host in track.customer.io api.customer.io; do
LATENCY=$(curl -s -o /dev/null -w "%{time_total}" "https://${host}")
echo "${host}: ${LATENCY}s&quDeploy Customer.
Customer.io Deploy Pipeline
Output
- A versioned, staged deployment of Customer.io configuration/code with owner approval, validation evidence, and a rollback reference.
- A protected promotion path that prevents unreviewed changes from affecting production recipients.
Examples
Validate an event/template configuration in development and staging with synthetic profiles, attach schema/template test results to the change, then release through an approved canary. If the canary shows wrong audience, consent, or rendering behavior, stop promotion and restore the last approved configuration.
Overview
Deploy Customer.io integrations to production: GCP Cloud Run with Secret Manager, Vercel serverless functions, AWS Lambda with SSM, Kubernetes with external secrets, plus health check endpoints and blue-green deployment scripts.
Prerequisites
- CI/CD pipeline configured (see
customerio-ci-integration) - Cloud platform credentials and access
- Production Customer.io credentials in a secrets manager
Instructions
Step 1: Deploy to Google Cloud Run
# .github/workflows/deploy-cloud-run.yml
name: Deploy to Cloud Run
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for Workload Identity Federation
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
service_account: ${{ secrets.WIF_SA }}
- uses: google-github-actions/setup-gcloud@v2
- name: Build and push
run: |
gcloud builds submit --tag gcr.io/${{ secrets.GCP_PROJECT }}/cio-service
- name: Deploy
run: |
gcloud run deploy cio-service \
--image gcr.io/${{ secrets.GCP_PROJECT }}/cio-service \
--region us-central1 \
--set-secrets "CUSTOMERIO_SITE_ID=cio-site-id:latest,\
CUSTOMERIO_TRACK_API_KEY=cio-track-key:latest,\
CUSTOMERIO_APP_API_KEY=cio-app-key:latest" \
--set-env-vars "CUSTOMERIO_REGION=us,NODE_ENV=production" \
--min-instances 1 \
--max-instances 10 \
--memory 512Mi \
--cpu 1 \
--allow-unauthenticated
Step 2: Health Check Endpoint
// routes/health.ts
import { TrackClient, RegionUS } from "customerio-node";
import { Router } from "express";
const router = Router();
router.get("/health", async (_req, res) => {
const checks: Record<string, { status: string; latency_ms?: number }> = {};
// Check Track API
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.Create a minimal working Customer.
Customer.io Hello World
Output
- A verified synthetic profile/event or message example in a non-production workspace.
- A minimal receipt showing environment, validation result, and cleanup without retaining recipient data.
Examples
Create a development-only profile using a synthetic identifier, submit one schema-valid event, and confirm its arrival in the development workspace. Remove the test profile or let its documented TTL expire. Never use a real customer email or production segment as a tutorial target.
Overview
Create a minimal working Customer.io integration: identify a user (create/update their profile), track an event, and send a transactional email. This covers the three fundamental Customer.io operations.
Prerequisites
customerio-nodeinstalled (npm install customerio-node)CUSTOMERIO_SITE_IDandCUSTOMERIO_TRACK_API_KEYconfiguredCUSTOMERIO_APP_API_KEYconfigured (for transactional email example)
Instructions
Step 1: Identify a User (Create/Update Profile)
// hello-customerio.ts
import { TrackClient, RegionUS } from "customerio-node";
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
// identify() creates the user if they don't exist, or updates if they do.
// The first argument is your internal user ID (immutable — use DB primary key).
await cio.identify("user-123", {
email: "hello@example.com", // Required for email campaigns
first_name: "Jane",
last_name: "Doe",
plan: "pro",
created_at: Math.floor(Date.now() / 1000), // Unix seconds, NOT milliseconds
});
console.log("User identified in Customer.io");
Key rules:
id(first arg) should be your immutable database ID — never use email as IDemailattribute is required if you want to send email campaignscreated_atmust be Unix timestamp in seconds (not ms) —Math.floor(Date.now() / 1000)- All custom attributes are stored on the user profile and usable in segments + Liquid templates
Step 2: Track an Event
// Track a custom event on the user's activity timeline.
// Events trigger campaigns — the event name must match exactly in the dashboard.
await cio.track("user-123", {
name: "signed_up", // snake_case, matches campaign trigger
data: {
signup_method: "google_oauth",
referral_source: "product_hunt",
timestamp: Math.floor(Date.now() / 1000),
},
});
console.log("Event tracked in Customer.io");
Install and configure Customer.
Customer.io Install & Auth
Output
- A scoped Customer.io credential reference for the intended workspace/environment, with a verified low-impact connection test.
- A secret-management and rotation record that keeps tokens out of repositories, shell history, and logs.
Examples
Inject the development workspace credential from the approved secret manager, validate it with a low-impact read or synthetic event, and record only the environment and result. If a token is exposed, revoke it immediately and issue a replacement; do not reuse it while attempting to scrub logs.
Overview
Set up the customerio-node SDK and configure authentication for Customer.io's two API surfaces: the Track API (identify users, track events) and the App API (transactional messages, broadcasts, data queries).
Prerequisites
- Node.js 18+ with npm/pnpm
- Customer.io account at https://fly.customer.io
- Site ID + Track API Key from Settings > Workspace Settings > API & Webhook Credentials
- App API Key (bearer token) from the same page — needed for transactional messages and broadcasts
Two API Keys, Two Clients
| Client | Auth Method | Key Source | Use For |
|---|---|---|---|
TrackClient |
Basic Auth (Site ID + API Key) | Track API credentials | identify(), track(), trackAnonymous(), suppress(), destroy() |
APIClient |
Bearer Token (App API Key) | App API credentials | sendEmail(), sendPush(), triggerBroadcast() |
Instructions
Step 1: Install the SDK
npm install customerio-node
The package exports TrackClient, APIClient, RegionUS, RegionEU, SendEmailRequest, and SendPushRequest.
Step 2: Configure Environment Variables
# .env — NEVER commit this file
CUSTOMERIO_SITE_ID=your-site-id-here
CUSTOMERIO_TRACK_API_KEY=your-track-api-key-here
CUSTOMERIO_APP_API_KEY=your-app-api-key-here
CUSTOMERIO_REGION=us # "us" or "eu"
Add .env to .gitignore if not already there.
Step 3: Create the Track Client
// lib/customerio.ts
import { TrackClient, RegionUS, RegionEU } from "customerio-node";
function getRegion() {
return process.env.CUSTOMERIO_REGION === "eu" ? RegionEU : RegionUS;
}
// Singleton — reuse across your app
let trackClient: TrackClient | null = null;
Identify and avoid Customer.
Customer.io Known Pitfalls
Prerequisites
- The intended workspace, consent/data classification, event schema, and deployment owner.
- A synthetic test profile and a redacted diagnostic process; never investigate with production recipients by default.
Instructions
- Identify the applicable pitfall before changing an event, campaign, segment, or integration setting.
- Validate schema, identity, consent, environment, and idempotency in development/staging.
- Make one reversible correction and verify its effect with synthetic data before promotion.
- Record recurring failures in reviewed runbooks/contracts rather than relying on ad hoc retries.
Output
- A documented prevention or correction for a specific delivery, data, consent, or integration pitfall.
Error Handling
| Condition | Safe response |
|---|---|
| Wrong environment or recipient scope | Stop sends, correct configuration, and assess/notify under the incident process. |
| Event schema changes unexpectedly | Quarantine invalid events and version the contract before replay. |
| Consent status is uncertain | Do not message or replay until it is verified. |
Examples
Before changing a campaign trigger, send a synthetic event with an idempotency key to development, verify the expected segment and message state, then promote through approved change control. Do not test trigger fixes on live recipient cohorts.
Overview
The 12 most common Customer.io integration mistakes, with the wrong pattern, the correct pattern, and why it matters. Use this as a code review checklist and developer onboarding reference.
The Pitfall Catalog
Pitfall 1: Wrong API Key Type
// WRONG — using Track API key for transactional messages
const api = new APIClient(process.env.CUSTOMERIO_TRACK_API_KEY!);
// Gets 401 because App API uses a DIFFERENT bearer token
// CORRECT — use the App API key
const api = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!);
Why: Customer.io has two separate authentication systems. Track API uses Basic Auth (Site ID + Track Key). App API uses Bearer Auth (App Key). They are not interchangeable.
Pitfall 2: Millisecond Timestamps
// WRONG — JavaScript Date.now() returns milliseconds
await cio.identify("user-1", {
created_at: Date.now(), // 1704067200000 → year 55976
});
// CORRECT — Customer.io expects Unix seconds
await cio.identify("user-1", {
created_at: Math.floor(Date.now() / 1000), // 1704067200
});
Why: Customer.io accepts millisecond values without error but interprets them as seconds, resulting in dates th
Implement Customer.
Customer.io Load & Scale
Prerequisites
- A baseline for event volume, queue depth, latency, error/rate-limit behavior, and an approved load window.
- Synthetic payloads, capacity owner, delivery/consent guardrails, and a rollback decision threshold.
Output
- A measured load/capacity result with bounded concurrency, rate-limit behavior, and owner-approved scale decision.
- A rollback/recovery record that prevents duplicate or unauthorized customer messaging.
Examples
Run a staged load test using synthetic profiles and fixed idempotency keys, gradually increase only within the provider limit, and record throughput, 429s, queue age, and processing errors. Stop and reduce load on error/ordering regression; never use a live recipient list as a load-test fixture.
Overview
Load testing and scaling strategies for high-volume Customer.io integrations: k6 load test scripts, scaling architecture selection based on volume tier, Kubernetes HPA autoscaling, message queue buffering, and rate-limit-aware batch processing.
Scaling Architecture by Volume
| Daily Events | Architecture | Key Components |
|---|---|---|
| < 100K | Direct API | Singleton client, retry, connection pooling |
| 100K - 1M | Batched API | Event queue, batch processor, rate limiter |
| 1M - 10M | Queue-backed | Redis/Kafka queue, worker pool, backpressure |
| > 10M | Distributed | Multiple workspaces, sharded queues, regional routing |
Customer.io rate limit is ~100 req/sec per workspace. Plan your architecture around this.
Instructions
Step 1: k6 Load Test Script
// load-tests/customerio.js
// Run: k6 run --vus 10 --duration 60s load-tests/customerio.js
import http from "k6/http";
import { check, sleep } from "k6";
import { Counter, Trend } from "k6/metrics";
const SITE_ID = __ENV.CUSTOMERIO_SITE_ID;
const API_KEY = __ENV.CUSTOMERIO_TRACK_API_KEY;
const BASE_URL = "https://track.customer.io/api/v1";
const AUTH = `${SITE_ID}:${API_KEY}`;
const identifyLatency = new Trend("cio_identify_latency");
const trackLatency = new Trend("cio_track_latency");
const errors = new Counter("cio_errors");
export const options = {
scenarios: {
identify_load: {
executor: "ramping-arrival-rate",
startRate: 10,
timeUnit: "1s",
preAllocatedVUs: 20,
maxVUs: 50,
stages: [
{ duration: "30s", target: 50 }, // Ramp to 50/sec
{ duration: "60s", target: 80 }, // Hold at 80/sec (near limit)
{ duration: "30s", target: 10 }, // Cool down
],
},
},
thresholConfigure Customer.
Customer.io Local Dev Loop
Output
- A small, test-backed local change using a non-production workspace and synthetic recipient data.
- A reviewed deployment/revert path that preserves production campaigns, consent, and contact data.
Examples
Use a development workspace and a synthetic profile such as test-user-001; validate the event schema locally, run the focused test, and inspect the resulting campaign behavior before committing. Never point local experiments at production segments or use real recipient data to debug a template.
Overview
Set up an efficient local development workflow for Customer.io: environment isolation via separate workspaces, a dry-run client for safe development, test mocks for unit tests, and prefixed events that never pollute production data.
Prerequisites
customerio-nodeinstalled- Separate Customer.io workspace for development (recommended — free workspaces available)
dotenvor similar for environment variable loading
Instructions
Step 1: Environment Configuration
# .env.development
CUSTOMERIO_SITE_ID=dev-site-id
CUSTOMERIO_TRACK_API_KEY=dev-track-key
CUSTOMERIO_APP_API_KEY=dev-app-key
CUSTOMERIO_REGION=us
CUSTOMERIO_DRY_RUN=false
CUSTOMERIO_EVENT_PREFIX=dev_
# .env.test
CUSTOMERIO_SITE_ID=not-needed
CUSTOMERIO_TRACK_API_KEY=not-needed
CUSTOMERIO_APP_API_KEY=not-needed
CUSTOMERIO_DRY_RUN=true
CUSTOMERIO_EVENT_PREFIX=test_
Step 2: Environment-Aware Client
// lib/customerio-dev.ts
import { TrackClient, APIClient, RegionUS, RegionEU } from "customerio-node";
interface CioConfig {
siteId: string;
trackApiKey: string;
appApiKey: string;
region: typeof RegionUS | typeof RegionEU;
dryRun: boolean;
eventPrefix: string;
}
function loadConfig(): CioConfig {
return {
siteId: process.env.CUSTOMERIO_SITE_ID ?? "",
trackApiKey: process.env.CUSTOMERIO_TRACK_API_KEY ?? "",
appApiKey: process.env.CUSTOMERIO_APP_API_KEY ?? "",
region: process.env.CUSTOMERIO_REGION === "eu" ? RegionEU : RegionUS,
dryRun: process.env.CUSTOMERIO_DRY_RUN === "true",
eventPrefix: process.env.CUSTOMERIO_EVENT_PREFIX ?? "",
};
}
export class DevTrackClient {
private client: TrackClient | null = null;
private config: CioConfig;
private log: typeof console.log;
constructor() {
this.config = loadConfig();
this.log = console.log.bind(console);
if (!this.config.dryRun) {
this.client = new TrackClient(
this.config.siteId,
this.config.trackApiKey,
{ region: this.config.region }
);
}
}
async identify(userId: string, attributes: Record<string, any>) {
const prefixedId = `${this.config.eventPrefix}${userId}`;
if (this.Configure Customer.
Customer.io Multi-Environment Setup
Output
- Isolated development, staging, and production workspaces with separate scoped credentials, segments, and delivery controls.
- A promotion receipt with synthetic validation, owner approval, and a rollback path that avoids production-recipient experiments.
Examples
Promote a campaign template by validating it against synthetic profiles in development, then staging, while keeping production workspace identifiers and secrets distinct. If staging delivery or consent checks fail, stop promotion and restore the previous approved template rather than editing the production campaign in place.
Overview
Configure isolated Customer.io environments for dev, staging, and production: separate workspaces per environment, typed configuration with validation, environment-aware client wrappers, Kubernetes ConfigMap overlays, and data isolation verification.
Prerequisites
- Customer.io account with multiple workspaces (create at fly.customer.io)
- Environment variable management (dotenv, secrets manager)
- CI/CD pipeline for per-environment deployment
Workspace Strategy
| Environment | Workspace Name | Purpose | Dry Run | Data |
|---|---|---|---|---|
| Local dev | myapp-dev |
Individual developer testing | Optional | Fake/test data |
| CI | myapp-ci |
Automated test runs | No | Auto-cleaned test data |
| Staging | myapp-staging |
Pre-production validation | No | Subset of real data |
| Production | myapp-prod |
Live users | No | Real user data |
Each workspace has its own Site ID, Track API Key, and App API Key. Create workspaces at Settings > Workspace Settings.
Instructions
Step 1: Typed Environment Configuration
// config/customerio.ts
import { RegionUS, RegionEU } from "customerio-node";
type CioEnvironment = "development" | "ci" | "staging" | "production";
interface CioEnvConfig {
siteId: string;
trackApiKey: string;
appApiKey: string;
region: typeof RegionUS | typeof RegionEU;
dryRun: boolean;
logLevel: "debug" | "info" | "warn" | "error";
eventPrefix: string; // Prefix events in non-prod to prevent confusion
}
function validateConfig(config: CioEnvConfig, env: CioEnvironment): void {
if (!config.siteId) throw new Error(`Missing CUSTOMERIO_SITE_ID for ${env}`);
if (!config.trackApiKey) throw new Error(`Missing CUSTOMERIO_TRACK_API_KEY for ${env}`);
if (env === "production" && config.dryRun) {
throw new ErSet up Customer.
Customer.io Observability
Output
- Bounded metrics and redacted traces for event acceptance, campaign delivery, errors, latency, and rate-limit headroom.
- An owned alert/runbook path for delivery, authentication, data-quality, and provider incidents.
Examples
Emit an aggregate counter for accepted events and failures by environment and endpoint, never by email address, customer ID, message body, API key, or full payload. Trigger a staging alert with a harmless test event, verify the on-call route, then restore the normal state and record the alert receipt.
Overview
Implement comprehensive observability for Customer.io integrations: Prometheus metrics (latency, error rates, delivery funnel), structured JSON logging with PII redaction, OpenTelemetry tracing, and Grafana dashboard definitions.
Prerequisites
- Customer.io integration deployed
- Prometheus + Grafana (or compatible metrics stack)
- Structured logging system (pino recommended)
Key Metrics to Track
| Metric | Type | Description | Alert Threshold |
|---|---|---|---|
cio_api_duration_ms |
Histogram | API call latency | p99 > 5000ms |
cio_api_requests_total |
Counter | Total API requests by operation | N/A (rate) |
cio_api_errors_total |
Counter | API errors by status code | > 1% error rate |
cio_email_sent_total |
Counter | Transactional + campaign emails | N/A |
cio_email_bounced_total |
Counter | Bounce count | > 5% of sends |
cio_email_complained_total |
Counter | Spam complaints | > 0.1% of sends |
cio_webhook_received_total |
Counter | Webhook events by metric type | N/A |
cio_queue_depth |
Gauge | Pending items in event queue | > 10K |
Instructions
Step 1: Prometheus Metrics
// lib/customerio-metrics.ts
import { Counter, Histogram, Gauge, Registry } from "prom-client";
const registry = new Registry();
export const cioMetrics = {
apiDuration: new Histogram({
name: "cio_api_duration_ms",
help: "Customer.io API call duration in milliseconds",
labelNames: ["operation", "status"] as const,
buckets: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000],
registers: [registry],
}),
apiRequests: new Counter({
name: "cio_api_requests_total",
help: "Total Customer.io API requests",
labelNames: ["operation"] as const,
Optimize Customer.
Customer.io Performance Tuning
Output
- A measured delivery/API performance baseline with a scoped optimization, owner, and rollback trigger.
- A capacity decision that preserves consent, message correctness, rate limits, and provider reliability.
Examples
Measure event throughput, response latency, error rate, and queue depth in a development workspace using synthetic payloads. Change one batch/concurrency parameter, compare against the baseline, and revert if errors, rate limits, or message ordering regress. Do not increase concurrency by replaying production recipient data.
Overview
Optimize Customer.io API performance for high-volume integrations: HTTP connection pooling, identify deduplication caching, event batching with flush control, fire-and-forget async tracking, and regional routing.
Prerequisites
- Working Customer.io integration
- Understanding of your traffic patterns and volume
- Monitoring to measure improvement (see
customerio-observability)
Performance Targets
| Operation | Baseline | Optimized | Technique |
|---|---|---|---|
| Single identify | ~200ms | ~80ms | Connection pooling |
| Single track | ~200ms | ~80ms | Connection pooling |
| 100 events batch | ~20s serial | ~500ms | Parallel batching |
| Duplicate identify | ~200ms | ~0ms | Dedup cache |
| Non-critical track | Blocking | Non-blocking | Fire-and-forget |
Instructions
Step 1: HTTP Connection Pooling
// lib/customerio-pooled.ts
import { TrackClient, RegionUS } from "customerio-node";
import https from "https";
// The customerio-node SDK creates new connections by default.
// Reuse connections with a keep-alive agent.
const agent = new https.Agent({
keepAlive: true,
maxSockets: 25, // Max concurrent connections
maxFreeSockets: 10, // Keep idle connections open
timeout: 30000, // 30s socket timeout
keepAliveMsecs: 15000, // TCP keep-alive probe interval
});
// Apply to the SDK by creating a singleton with the agent
// Note: customerio-node doesn't directly accept an agent,
// but we configure Node.js global agent for HTTPS
https.globalAgent = agent;
// Singleton client — one instance = one connection pool
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
export { cio };
Step 2: Identify Deduplication Cache
// lib/customerio-dedup.ts
// Skip duplicate identify() calls within a time window
class LRUCache<K, V> {
private map = new Map<K, Implement Customer.
Customer.io Primary Workflow
Output
- A consent-aware workflow with explicit trigger, audience, data contract, message/template version, and owner.
- A synthetic validation receipt and a scoped rollback/disable action for unexpected delivery behavior.
Examples
Build the workflow in development using one synthetic profile and a versioned event, confirm trigger conditions and suppression/consent rules, then promote through staging. Enable production through an approved canary audience; pause/disable the workflow if the audience or message behavior does not match the reviewed expectation.
Overview
Implement Customer.io's core messaging workflow: identify users with segment-ready attributes, track lifecycle events that trigger campaigns, and set up the data layer for automated onboarding, nurture, and re-engagement sequences.
Prerequisites
customerio-nodeconfigured with Track API credentials- Campaigns created in Customer.io dashboard (triggered by events you define)
- Understanding of your user lifecycle stages
How Campaigns Work
Your App (SDK) Customer.io Dashboard User
───────────── ──────────────────── ────
cio.identify(user) → Profile created/updated
cio.track("signed_up") → Campaign trigger fires
Wait 1 day → Welcome email
Check: verified?
├─ No → Verification reminder
└─ Yes → Wait 3 days → Feature tips email
Events tracked via the SDK trigger campaigns you build in the dashboard. The SDK sends the data; the dashboard defines the workflow logic.
Instructions
Step 1: Define Your Event Taxonomy
// lib/customerio-events.ts
import { TrackClient, RegionUS } from "customerio-node";
// Central event definitions — every event your app tracks
export const CIO_EVENTS = {
// Onboarding
SIGNED_UP: "signed_up",
EMAIL_VERIFIED: "email_verified",
PROFILE_COMPLETED: "profile_completed",
FIRST_PROJECT_CREATED: "first_project_created",
// Engagement
FEATURE_USED: "feature_used",
INVITED_TEAMMATE: "invited_teammate",
UPGRADE_STARTED: "upgrade_started",
UPGRADE_COMPLETED: "upgrade_completed",
// Lifecycle
SUBSCRIPTION_RENEWED: "subscription_renewed",
SUBSCRIPTION_CANCELLED: "subscription_cancelled",
TRIAL_EXPIRING: "trial_expiring",
// Commerce
CHECKOUT_STARTED: "checkout_started",
CHECKOUT_COMPLETED: "checkout_completed",
REFUND_REQUESTED: "refund_requested",
} as const;
type EventName = (typeof CIO_EVENTS)[keyExecute Customer.
Customer.io Production Checklist
Instructions
- Complete every production gate with an evidence link: workspace/environment, identity, secret scope, consent, event/schema, template review, observability, rate limits, and rollback.
- Verify the exact production audience and suppression/consent behavior using approved safe checks.
- Hold release when any data, identity, delivery, or owner gate is unverified.
- Record the named go/no-go decision and post-deployment observation result.
Output
- A production-readiness receipt with owners, evidence, exceptions, and tested rollback path.
Examples
Before enabling a campaign, validate it in staging with synthetic profiles, confirm the production segment and consent/suppression logic under review, and use an approved canary audience. If the canary differs from expected behavior, stop the launch and restore the previous version rather than widening sends.
Overview
Comprehensive go-live checklist for Customer.io integrations: credentials audit, integration quality review, email deliverability setup, monitoring configuration, smoke tests, and staged rollout plan.
Prerequisites
- Customer.io integration complete and tested in staging
- Production workspace credentials ready
- Sending domain configured and verified
Production Checklist
1. Credentials & Configuration
| Item | Check | How to Verify |
|---|---|---|
| Production Site ID | Correct workspace | Settings > API & Webhook Credentials |
| Production Track API Key | Different from dev/staging | Compare with staging .env |
| Production App API Key | Set for transactional messages | Test with curl bearer auth |
| Region setting | Matches account region (US/EU) | Settings > Workspace Settings |
| Secrets storage | In secrets manager, not .env |
Check deployment config |
| Key rotation schedule | Documented (90-day cycle) | Calendar reminder set |
2. Integration Quality
// scripts/prod-audit.ts
import { TrackClient, RegionUS } from "customerio-node";
async function auditIntegration() {
const checks: { name: string; pass: boolean; detail: string }[] = [];
// Check 1: Credentials exist
const siteId = process.env.CUSTOMERIO_SITE_ID;
const trackKey = process.env.CUSTOMERIO_TRACK_API_KEY;
const appKey = process.env.CUSTOMERIO_APP_API_KEY;
checks.push({
name: "Track credentials",
pass: !!(siteId && trackKey),
detail: siteId ? `Site ID: ${siteId.substring(0, 4)}...` : "MISSING",
});
checks.push({Implement Customer.
Customer.io Rate Limits
Prerequisites
- Current provider limit documentation/plan, a measured baseline, idempotency design, and a named capacity owner.
- Synthetic test payloads and alerting for 429s, queue age, delivery failure, and duplicate-event risk.
Output
- A rate-aware client/workflow configuration with bounded concurrency, backoff, idempotency, monitoring, and a safe replay decision path.
- An aggregate load-test or incident receipt showing limit behavior without recipient payloads.
Examples
Send synthetic events at gradually increasing concurrency below the approved limit, record 429s and queue age, and apply exponential backoff with jitter using a stable idempotency key. If throttling occurs, reduce concurrency and allow recovery; do not retry every failed event immediately or widen sends to compensate.
Overview
Understand Customer.io's API rate limits and implement proper throttling: token bucket limiters, exponential backoff with jitter, queue-based processing, and 429 response handling.
Rate Limit Reference
| API | Endpoint | Limit | Scope |
|---|---|---|---|
| Track API | identify, track, trackAnonymous |
~100 req/sec | Per workspace |
| Track API | Batch operations | ~100 req/sec | Per workspace |
| App API | Transactional email/push | ~100 req/sec | Per workspace |
| App API | Broadcasts, queries | ~10 req/sec | Per workspace |
These are approximate. Customer.io uses sliding window rate limiting. When exceeded, you get a 429 Too Many Requests response.
Instructions
Step 1: Token Bucket Rate Limiter
// lib/rate-limiter.ts
export class TokenBucket {
private tokens: number;
private lastRefill: number;
constructor(
private readonly maxTokens: number = 80, // Stay under 100/sec limit
private readonly refillRate: number = 80 // Tokens per second
) {
this.tokens = maxTokens;
this.lastRefill = Date.now();
}
private refill(): void {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
this.lastRefill = now;
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens >= 1) {
this.tokens -= 1;
return;
}
// Wait until a token is available
const waitMs = ((1 - this.tokens) / this.refillRate) * 1000;
await new Promise((r) => setTimeout(r, Math.ceil(waitMs)));
this.tokens = 0;
this.lastRefill = Date.now();
}
}
Step 2: Exponential Backoff with Jitter
customerio-reference-architecture
View full skill →
Implement Customer.
ReadWriteEditBash(npm:*)Bash(npx:*)GlobGrep
Customer.io Reference Architecture
Prerequisites
- A data-flow owner, approved workspace/environment boundaries, event contract, consent model, and incident/support route.
- Repository/configuration review for any change to credentials, webhooks, message templates, or customer-data processing.
Output
- A documented architecture mapping event producers, identity/consent, Customer.io workspaces, observability, ownership, and failure handling.
- Clear trust boundaries and reversible integration points for delivery, replay, and incident response.
Examples
Document a producer that validates and idempotently emits a versioned event to the development workspace, with a consent gate before campaign entry and redacted metrics by environment. Promote the same contract through staging before production, retain a dead-letter/replay owner, and keep recipient identifiers and tokens outside logs and diagrams.
Overview
Enterprise-grade reference architecture for Customer.io: a service layer separating Track and App API concerns, event-driven processing with message queues, repository pattern for user-to-CIO sync, webhook event bus, and infrastructure as code.
Architecture Principles
- Two Clients, Two Concerns —
TrackClient for behavioral data in, APIClient for messages out
- Event-Driven — Message queues decouple your app from Customer.io API availability
- Idempotent Operations — All writes safely retryable via content hashing
- Service Layer — Business logic never calls Customer.io SDK directly
- Observability — Every operation emits timing and error metrics
Architecture Diagram
┌─────────────┐ ┌───────────────────┐ ┌──────────────┐
│ Application │───>│ MessagingService │───>│ Track API │
│ Routes │ │ (service layer) │ │ identify() │
└─────────────┘ │ │ │ track() │
│ - identify users │ └──────────────┘
│ - track events │
│ - send txn emails │ ┌──────────────┐
│ │───>│ App API │
└───────────────────┘ │ sendEmail() │
│ │ broadcast() │
│ └──────────────┘
v
┌───────────────────┐
│ Event Queue │ ┌──────────────┐
│ (Redis/Kafka) │───>│ DLQ │
│ for reliability │ │ (failures) │
└───────────────────┘ └──────────────┘
┌─────────────┐ ┌───────────────────┐ ┌──────────────┐
│ Customer.io │───>│ Webhook Handler │───>│ BigQuery │
│ Webh
Implement Customer.
Customer.io Reliability Patterns
Output
- An idempotent, observable event-delivery design with owned retry, replay, and dead-letter decisions.
- A recovery procedure that protects consent, ordering, and recipient experience during partial failures.
Error Handling
| Condition | Safe response |
|---|---|
| Provider returns transient failure | Retry with bounded exponential backoff and preserve the idempotency key. |
| Event may be duplicated | Deduplicate by the stable event/correlation key before triggering downstream action. |
| Replay could resend customer messaging | Require consent/segment review and replay only the scoped failed set. |
| Dead-letter queue grows | Alert the owner, quarantine malformed payloads, and fix the contract before bulk replay. |
Examples
Store an immutable event ID and delivery state, retry 429/5xx responses with bounded backoff, and send terminal failures to a protected dead-letter queue. After the schema fix, replay only those records whose consent and idempotency checks still pass.
Overview
Implement fault-tolerant Customer.io integrations: circuit breaker (stop cascading failures), retry with jitter (handle transient errors), fallback queue (survive outages), idempotency guard (prevent duplicates), and graceful degradation (never crash your app for analytics).
Prerequisites
- Working Customer.io integration
- Understanding of failure modes (429, 5xx, timeouts, DNS failures)
- Redis (recommended for queue-based patterns)
Instructions
Pattern 1: Circuit Breaker
// lib/circuit-breaker.ts
type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
export class CircuitBreaker {
private state: CircuitState = "CLOSED";
private failureCount = 0;
private successCount = 0;
private lastFailureTime = 0;
constructor(
private readonly failureThreshold: number = 5,
private readonly successThreshold: number = 3,
private readonly resetTimeoutMs: number = 30000
) {}
get currentState(): CircuitState {
if (this.state === "OPEN") {
// Check if enough time has passed to try again
if (Date.now() - this.lastFailureTime > this.resetTimeoutMs) {
this.state = "HALF_OPEN";
this.successCount = 0;
}
}
return this.state;
}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.currentState === "OPEN") {
throw new Error("Circuit breaker is OPEN — Customer.io calls blocked");
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (err) {
this.onFailure();
throw err;
}Apply production-ready Customer.
Customer.io SDK Patterns
Output
- A reusable SDK integration pattern with scoped configuration, schema validation, idempotency, observability, and error classification.
- A test-backed client boundary that keeps credentials and recipient data out of logs and source control.
Examples
Instantiate the client from an environment-specific secret reference, validate a synthetic event against the contract, attach a stable idempotency key, and assert the SDK call in a mocked unit test. For a development integration test, use a synthetic profile and record only result status/correlation ID; never log the client token or full event payload.
Overview
Production-ready patterns for customerio-node: type-safe wrappers with enum-constrained events, retry with exponential backoff, event batching for high-volume scenarios, and singleton lifecycle management.
Prerequisites
customerio-nodeinstalled- TypeScript project (recommended for type-safe patterns)
- Understanding of your event taxonomy
Instructions
Pattern 1: Type-Safe Client Wrapper
// lib/customerio-typed.ts
import { TrackClient, RegionUS, RegionEU } from "customerio-node";
// Define your event taxonomy as a union type
type CioEvent =
| { name: "signed_up"; data: { method: string; source?: string } }
| { name: "plan_changed"; data: { from: string; to: string; mrr: number } }
| { name: "feature_used"; data: { feature: string; duration_ms?: number } }
| { name: "checkout_completed"; data: { order_id: string; total: number; items: number } }
| { name: "subscription_cancelled"; data: { reason: string; feedback?: string } };
// Define user attributes with strict types
interface CioUserAttributes {
email: string;
first_name?: string;
last_name?: string;
plan?: "free" | "starter" | "pro" | "enterprise";
company?: string;
created_at?: number; // Unix seconds
last_seen_at?: number; // Unix seconds
[key: string]: unknown; // Allow additional attributes
}
export class TypedCioClient {
private client: TrackClient;
constructor(siteId: string, apiKey: string, region: "us" | "eu" = "us") {
this.client = new TrackClient(siteId, apiKey, {
region: region === "eu" ? RegionEU : RegionUS,
});
}
async identify(userId: string, attributes: CioUserAttributes): Promise<void> {
await this.client.identify(userId, {
...attributes,
last_seen_at: Math.floor(Date.now() / 1000),
});
}
async track(userId: string, event: CioEvent): Promise<void> {
await this.client.track(userId, {
name: event.name,
data: { ...event.data, tracked_at: Math.floor(Date.now() / 1000) },
});
}
async supprApply Customer.
Customer.io Security Basics
Output
- A least-privilege Customer.io integration with scoped secrets, approved data attributes, and auditable access ownership.
- A tested incident path for credential exposure, unauthorized delivery, and sensitive-data handling.
Examples
Configure a development workspace secret through the approved manager and send a synthetic event that contains no recipient PII. Verify the event uses the intended workspace and attribute allowlist. If a token or customer attribute appears in source or logs, revoke/contain first, then investigate and replace it.
Overview
Implement security best practices for Customer.io: secrets management for API credentials, PII sanitization before sending data, webhook signature verification (HMAC-SHA256), API key rotation, and GDPR/CCPA data deletion compliance.
Prerequisites
- Customer.io account with admin access
- Understanding of your data classification (what is PII)
- Secrets management system (recommended for production)
Instructions
Step 1: Secure Credential Storage
// lib/customerio-secrets.ts
// NEVER hardcode credentials — use environment variables or a secrets manager
// Option A: Environment variables (acceptable for most apps)
const siteId = process.env.CUSTOMERIO_SITE_ID;
const trackKey = process.env.CUSTOMERIO_TRACK_API_KEY;
// Option B: GCP Secret Manager (recommended for production)
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
const secretClient = new SecretManagerServiceClient();
async function getSecret(name: string): Promise<string> {
const [version] = await secretClient.accessSecretVersion({
name: `projects/my-project/secrets/${name}/versions/latest`,
});
return version.payload?.data?.toString() ?? "";
}
async function createCioClient() {
const [siteId, trackKey] = await Promise.all([
getSecret("customerio-site-id"),
getSecret("customerio-track-api-key"),
]);
return new TrackClient(siteId, trackKey, { region: RegionUS });
}
Step 2: PII Sanitization
// lib/customerio-sanitize.ts
// Sanitize user data BEFORE sending to Customer.io
const NEVER_SEND = new Set([
"ssn", "social_security", "tax_id",
"credit_card", "card_number", "cvv",
"password", "password_hash",
"bank_account", "routing_number",
]);
const HASH_FIELDS = new Set([
"phone", "phone_number",
"ip_address", "ip",
"address", "street_address",
]);
import { createHash } from "crypto";
function hashValue(value: string): string {
return createHash("sha256").update(value).digest("hex").substrPlan and execute Customer.
Customer.io Upgrade & Migration
Output
- A staged migration record with schema/template compatibility, environment evidence, owner, and rollback decision.
- A preserved prior configuration and safe observation window before any irreversible cleanup.
Examples
Migrate a development workspace first using synthetic profiles and versioned event/template contracts. Verify delivery, consent, attributes, and observability before staging and production canaries. If compatibility or recipient behavior differs, roll back to the prior version and stop promotion; do not bulk-replay production events to test a migration.
Current State
!npm list customerio-node 2>/dev/null | grep customerio || echo 'customerio-node: not installed' !npm view customerio-node version 2>/dev/null || echo 'Cannot check latest version'
Overview
Plan and execute customerio-node SDK upgrades safely: assess current version, review breaking changes, apply code migrations, and validate with staged rollout.
Prerequisites
- Current SDK version identified (
npm list customerio-node) - Test environment available
- Version control for rollback
Major Version Migration Reference
Legacy CustomerIO to Modern TrackClient + APIClient
Older versions of customerio-node used a single CustomerIO class. Modern versions split into TrackClient (tracking) and APIClient (transactional/broadcasts).
// BEFORE — Legacy pattern (customerio-node < 2.x)
const CustomerIO = require("customerio-node");
const cio = new CustomerIO(siteId, apiKey);
cio.identify("user-1", { email: "user@example.com" });
cio.track("user-1", { name: "event_name" });
// AFTER — Modern pattern (customerio-node >= 2.x)
import { TrackClient, APIClient, RegionUS } from "customerio-node";
const cio = new TrackClient(siteId, apiKey, { region: RegionUS });
await cio.identify("user-1", { email: "user@example.com" });
await cio.track("user-1", { name: "event_name", data: {} });
const api = new APIClient(appApiKey, { region: RegionUS });
await api.sendEmail(request);
Key changes:
TrackClientreplacesCustomerIOfor identify/trackAPIClientis new — handles transactional + broadcasts- Region is now explicit (
RegionUSorRegionEU) - Methods return Promises (must
await) - Event tracking uses
{ name, data }object instead of positional args
Instructions
Step 1: Assess Current Version
customerio-webhooks-events
View full skill →
Implement Customer.
ReadWriteEditBash(npm:*)Bash(npx:*)GlobGrep
Customer.io Webhooks & Events
Output
- A validated, signed, idempotent event/webhook flow with redacted observability and an owned retry policy.
- A delivery receipt that records correlation ID, environment, event type, result, and consent-safe recovery decision.
Examples
Verify a webhook signature before parsing payload data, store only the opaque event ID for deduplication, and acknowledge within the provider timeout. For a failed processing attempt, retry with bounded backoff; send terminal failures to a protected queue and replay only after schema and consent checks pass.
Overview
Implement Customer.io reporting webhook handling: receive real-time delivery events (sent, delivered, opened, clicked, bounced, complained, unsubscribed), verify HMAC-SHA256 signatures, process events reliably with queuing, and stream to a data warehouse.
How Reporting Webhooks Work
Customer.io Your Server Data Warehouse
────────── ─────────── ──────────────
Email sent → POST /webhooks/cio → Verify signature
Email opened → POST /webhooks/cio → Parse event type
Link clicked → POST /webhooks/cio → Route to handler → INSERT INTO events
Email bounced → POST /webhooks/cio → Suppress user
Configure at: Data & Integrations > Integrations > Reporting Webhooks
Prerequisites
- Public HTTPS endpoint for webhook receiver
- Webhook signing key from Customer.io dashboard
- Express or similar HTTP framework
Instructions
Step 1: Define Webhook Event Types
// types/customerio-webhooks.ts
// Customer.io reporting webhook event metrics
type CioMetric =
| "sent" // Message sent to delivery provider
| "delivered" // Delivery provider confirmed receipt
| "opened" // Recipient opened the email
| "clicked" // Recipient clicked a link
| "converted" // Recipient completed a conversion goal
| "bounced" // Email bounced (hard or soft)
| "spammed" // Recipient marked as spam
| "unsubscribed" // Recipient unsubscribed
| "dropped" // Message dropped (suppressed, invalid)
| "deferred" // Delivery temporarily deferred
| "failed"; // Delivery failed
interface CioWebhookEvent {
// Event metadata
event_id: string;
metric: CioMetric;
timestamp: number; // Unix seconds
// Recipient info
customer_id: string;
email_address?: string;
// Message info
subject?: string;
template_id?: number;
campaign_id?: number;
broadcast_id?: number;
action_id?: number;
// Delivery details
delivery_id?: string;
// Link tracking (for "clicked" events)
href?: string;
li
How It Works
import { TrackClient, APIClient, SendEmailRequest, RegionUS } from "customerio-node";
// Track API -- identify users and track events
const cio = new TrackClient(siteId, trackApiKey, { region: RegionUS });
await cio.identify("user-123", { email: "jane@example.com", plan: "pro" });
await cio.track("user-123", { name: "signed_up", data: { method: "google" } });
// App API -- send transactional messages
const api = new APIClient(appApiKey, { region: RegionUS });
await api.sendEmail(new SendEmailRequest({
to: "jane@example.com",
transactional_message_id: "1",
message_data: { name: "Jane" },
identifiers: { id: "user-123" },
}));
Ready to use customerio-pack?
Related Plugins
supabase-pack
Complete Supabase integration skill pack with 30 skills covering authentication, database, storage, realtime, edge functions, and production operations. Flagship+ tier vendor pack.
/plugin install supabase-pack@claude-code-plugins-plus
vercel-pack
Complete Vercel integration skill pack with 30 skills covering deployments, edge functions, preview environments, performance optimization, and production operations. Flagship+ tier vendor pack.
/plugin install vercel-pack@claude-code-plugins-plus
clay-pack
Complete Clay integration skill pack with 30 skills covering data enrichment, waterfall workflows, AI agents, and GTM automation. Flagship+ tier vendor pack.
/plugin install clay-pack@claude-code-plugins-plus
cursor-pack
Complete Cursor integration skill pack with 30 skills covering AI code editing, composer workflows, codebase indexing, and productivity features. Flagship+ tier vendor pack.
/plugin install cursor-pack@claude-code-plugins-plus
exa-pack
Complete Exa integration skill pack with 30 skills covering neural search, semantic retrieval, web search API, and AI-powered discovery. Flagship+ tier vendor pack.
/plugin install exa-pack@claude-code-plugins-plus
firecrawl-pack
Complete Firecrawl integration skill pack with 30 skills covering web scraping, crawling, markdown conversion, and LLM-ready data extraction. Flagship+ tier vendor pack.
/plugin install firecrawl-pack@claude-code-plugins-plus