Skip to main contentcustomerio-reliability-patterns
Implement Customer.io reliability and fault-tolerance patterns.
Use when building circuit breakers, fallback queues, idempotency,
or graceful degradation for Customer.io integrations.
Trigger: "customer.io reliability", "customer.io resilience",
"customer.io circuit breaker", "customer.io fault tolerance".
설치로 이동 Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill customerio-reliability-patterns명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
이 저장소의 다른 Skills
Implement user sign-up and sign-in flows with Clerk.
Use when building authentication UI, customizing sign-in experience,
or implementing OAuth social login.
Trigger with phrases like "clerk sign-in", "clerk sign-up",
"clerk login flow", "clerk OAuth", "clerk social login".
Implement session management and middleware with Clerk.
Use when managing user sessions, configuring route protection,
or implementing token refresh and custom JWT templates.
Trigger with phrases like "clerk session", "clerk middleware",
"clerk route protection", "clerk token", "clerk JWT".
Configure enterprise SSO, role-based access control, and organization management.
Use when implementing SSO integration, configuring role-based permissions,
or setting up organization-level controls.
Trigger with phrases like "clerk SSO", "clerk RBAC",
"clerk enterprise", "clerk roles", "clerk permissions", "clerk organizations".

jeremylongshore
jeremylongshore/claude-code-plugins-plus-skills
GitHub 저장소 열기| name | customerio-reliability-patterns |
| description | Implement Customer.io reliability and fault-tolerance patterns.
Use when building circuit breakers, fallback queues, idempotency,
or graceful degradation for Customer.io integrations.
Trigger: "customer.io reliability", "customer.io resilience",
"customer.io circuit breaker", "customer.io fault tolerance".
|
| allowed-tools | Read, Write, Edit, Bash(npm:*), Bash(npx:*), Glob, Grep |
| version | 1.14.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","customer-io","reliability","circuit-breaker","resilience"] |
| compatibility | Designed for Claude Code, also compatible with Codex and OpenClaw |
Customer.io Reliability Patterns
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
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. === ) {
(.() - . > .) {
. = ;
. = ;
}
}
.;
}
execute<T>(: <T>): <T> {
(. === ) {
();
}
{
result = ();
.();
result;
} (err) {
.();
err;
}
}
(): {
. = ;
(. === ) {
.++;
(. >= .) {
. = ;
.();
}
}
}
(): {
.++;
. = .();
(. >= .) {
. = ;
.(
+
);
}
}
(): { : ; : ; : | } {
{
: .,
: .,
: . ? (.) : ,
};
}
}
state
"OPEN"
if
Date
now
this
lastFailureTime
this
resetTimeoutMs
this
state
"HALF_OPEN"
this
successCount
0
return
this
state
async
fn
() =>
Promise
Promise
if
this
currentState
"OPEN"
throw
new
Error
"Circuit breaker is OPEN — Customer.io calls blocked"
try
const
await
fn
this
onSuccess
return
catch
this
onFailure
throw
private
onSuccess
void
this
failureCount
0
if
this
state
"HALF_OPEN"
this
successCount
if
this
successCount
this
successThreshold
this
state
"CLOSED"
console
log
"Circuit breaker: CLOSED (recovered)"
private
onFailure
void
this
failureCount
this
lastFailureTime
Date
now
if
this
failureCount
this
failureThreshold
this
state
"OPEN"
console
warn
`Circuit breaker: OPEN (${this.failureCount} failures). `
`Will retry in ${this.resetTimeoutMs / 1000}s`
getStatus
state
CircuitState
failures
number
lastFailure
Date
null
return
state
this
currentState
failures
this
failureCount
lastFailure
this
lastFailureTime
new
Date
this
lastFailureTime
null
Pattern 2: Retry with Jitter
export async function retryWithJitter<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
const status = err.statusCode ?? err.status;
if (status >= 400 && status < 500 && status !== 429) throw err;
if (attempt === maxRetries) throw err;
const delay = baseDelayMs * Math.pow(2, attempt);
const jitter = delay * 0.3 * Math.random();
await new Promise((r) => setTimeout(r, delay + jitter));
}
}
throw new Error("Unreachable");
}
Pattern 3: Fallback Queue
import { Queue, Worker } from "bullmq";
import { TrackClient, RegionUS } from "customerio-node";
const REDIS_URL = process.env.REDIS_URL ?? "redis://localhost:6379";
const fallbackQueue = new Queue("cio:fallback", {
connection: { url: REDIS_URL },
defaultJobOptions: {
attempts: 10,
backoff: { type: "exponential", delay: 60000 },
removeOnComplete: 1000,
removeOnFail: 5000,
},
});
export async function enqueueFallback(
operation: "identify" | "track" | "suppress",
data: Record<string, any>
): Promise<void> {
await fallbackQueue.add(operation, data);
console.log(`CIO fallback: queued ${operation} (circuit open)`);
}
export function startFallbackWorker(): void {
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
new Worker("cio:fallback", async (job) => {
switch (job.name) {
case "identify":
await cio.identify(job.data.userId, job.data.attrs);
break;
case "track":
await cio.track(job.data.userId, job.data.event);
break;
case "suppress":
await cio.suppress(job.data.userId);
break;
}
}, {
connection: { url: REDIS_URL },
concurrency: 5,
});
}
Pattern 4: Resilient Client (All Patterns Combined)
import { TrackClient, RegionUS } from "customerio-node";
import { CircuitBreaker } from "./circuit-breaker";
import { retryWithJitter } from "./retry";
import { enqueueFallback } from "./customerio-fallback";
export class ResilientCioClient {
private client: TrackClient;
private breaker: CircuitBreaker;
constructor(siteId: string, apiKey: string) {
this.client = new TrackClient(siteId, apiKey, { region: RegionUS });
this.breaker = new CircuitBreaker(5, 3, 30000);
}
async identify(userId: string, attrs: Record<string, any>): Promise<void> {
try {
await this.breaker.execute(() =>
retryWithJitter(() => this.client.identify(userId, attrs))
);
} catch (err: any) {
if (err.message.includes("Circuit breaker is OPEN")) {
await enqueueFallback("identify", { userId, attrs });
return;
}
console.error(`CIO identify failed for ${userId}: ${err.message}`);
}
}
async track(
userId: string,
name: string,
data?: Record<string, any>
): Promise<void> {
try {
await this.breaker.execute(() =>
retryWithJitter(() =>
this.client.track(userId, { name, data })
)
);
} catch (err: any) {
if (err.message.includes("Circuit breaker is OPEN")) {
await enqueueFallback("track", { userId, event: { name, data } });
return;
}
console.error(`CIO track failed for ${userId}/${name}: ${err.message}`);
}
}
getHealthStatus() {
return this.breaker.getStatus();
}
}
Pattern 5: Idempotency Guard
import { createHash } from "crypto";
const processedOps = new Map<string, number>();
const MAX_ENTRIES = 100_000;
const TTL_MS = 5 * 60 * 1000;
export function isIdempotent(
operation: string,
userId: string,
data: any
): boolean {
const hash = createHash("sha256")
.update(`${operation}:${userId}:${JSON.stringify(data)}`)
.digest("hex")
.substring(0, 16);
const existing = processedOps.get(hash);
if (existing && Date.now() - existing < TTL_MS) {
return true;
}
processedOps.set(hash, Date.now());
if (processedOps.size > MAX_ENTRIES) {
const cutoff = Date.now() - TTL_MS;
for (const [key, time] of processedOps) {
if (time < cutoff) processedOps.delete(key);
}
}
return false;
}
Pattern 6: Health Check Endpoint
import { ResilientCioClient } from "../lib/customerio-resilient";
const cio = new ResilientCioClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!
);
app.get("/health/customerio", (_req, res) => {
const status = cio.getHealthStatus();
const healthy = status.state === "CLOSED";
res.status(healthy ? 200 : 503).json({
customerio: {
circuit_state: status.state,
failure_count: status.failures,
last_failure: status.lastFailure?.toISOString() ?? null,
},
});
});
Pattern Selection Guide
| Scenario | Pattern | Priority |
|---|
| Transient 5xx errors | Retry with jitter | Must have |
| Extended Customer.io outage | Circuit breaker + fallback queue | Must have |
| Duplicate events from retries | Idempotency guard | Should have |
| App must never crash for tracking | Graceful degradation (catch all) | Must have |
| Need visibility into reliability | Health check endpoint | Should have |
Reliability Checklist
Resources
Next Steps
After reliability patterns, proceed to customerio-load-scale for load testing and scaling.