Skip to main content
clay-reliability-patterns Build fault-tolerant Clay integrations with circuit breakers, dead letter queues, and graceful degradation.
Use when building production Clay pipelines that need resilience,
implementing retry strategies, or adding fault tolerance to enrichment workflows.
Trigger with phrases like "clay reliability", "clay circuit breaker", "clay resilience",
"clay fallback", "clay fault tolerance", "clay dead letter queue".
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill clay-reliability-patternsEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Explorador de archivos
3 archivos Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
name clay-reliability-patterns description Build fault-tolerant Clay integrations with circuit breakers, dead letter queues, and graceful degradation.
Use when building production Clay pipelines that need resilience,
implementing retry strategies, or adding fault tolerance to enrichment workflows.
Trigger with phrases like "clay reliability", "clay circuit breaker", "clay resilience",
"clay fallback", "clay fault tolerance", "clay dead letter queue".
allowed-tools Read, Write, Edit, Bash(curl:*) version 1.14.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","clay","clay-reliability"] compatibility Designed for Claude Code, also compatible with Codex and OpenClaw
Clay Reliability Patterns
Overview
Production reliability patterns for Clay data enrichment pipelines. Clay's async enrichment model, credit-based billing, and dependency on 150+ external data providers require specific resilience strategies: credit budget circuit breakers, webhook delivery tracking, dead letter queues for failed batches, and graceful degradation when Clay is unavailable.
Prerequisites
Clay integration in production or pre-production
Redis or similar for state tracking
Understanding of Clay's async enrichment model
Monitoring infrastructure (see clay-observability)
Instructions
Step 1: Credit Budget Circuit Breaker
Stop processing when credit burn exceeds budget to prevent runaway costs:
class CreditCircuitBreaker {
private state : 'closed' | 'open' | 'half-open' = 'closed' ;
private dailyCreditsUsed = 0 ;
private failureCount = 0 ;
private lastFailureAt : Date | null = null ;
private readonly cooldownMs : number ;
constructor (
private dailyLimit : number ,
private failureThreshold : number = 5 ,
cooldownMinutes : number = 15 ,
) {
this .cooldownMs = cooldownMinutes * * ;
}
( : ): { : ; ?: } {
( . === ) {
( . && . () - . . () > . ) {
. = ;
. ( );
} {
{ : , : };
}
}
( . + estimatedCredits > . ) {
{ : , : };
}
{ : };
}
( ) {
. += creditsUsed;
( . === ) {
. = ;
. = ;
. ( );
}
}
( ) {
. ++;
. = ();
( . >= . ) {
. = ;
. ( );
}
}
( ) {
. = ;
}
}
60
1000
canProcess
estimatedCredits
number
allowed
boolean
reason
string
if
this
state
'open'
if
this
lastFailureAt
Date
now
this
lastFailureAt
getTime
this
cooldownMs
this
state
'half-open'
console
log
'Circuit breaker: half-open (testing)'
else
return
allowed
false
reason
`Circuit OPEN. Cooldown until ${new Date (this .lastFailureAt!.getTime() + this .cooldownMs).toISOString()} `
if
this
dailyCreditsUsed
this
dailyLimit
return
allowed
false
reason
`Daily credit limit reached: ${this .dailyCreditsUsed} /${this .dailyLimit} `
return
allowed
true
recordSuccess
creditsUsed : number
this
dailyCreditsUsed
if
this
state
'half-open'
this
state
'closed'
this
failureCount
0
console
log
'Circuit breaker: closed (recovered)'
recordFailure
this
failureCount
this
lastFailureAt
new
Date
if
this
failureCount
this
failureThreshold
this
state
'open'
console
error
`Circuit breaker: OPEN after ${this .failureCount} failures`
resetDaily
this
dailyCreditsUsed
0
Step 2: Dead Letter Queue for Failed Submissions
interface DLQEntry {
row : Record <string , unknown >;
error : string ;
webhookUrl : string ;
failedAt : string ;
retryCount : number ;
maxRetries : number ;
}
class ClayDLQ {
private entries : DLQEntry [] = [];
addToQueue (row : Record <string , unknown >, error : string , webhookUrl : string ): void {
this .entries .push ({
row,
error,
webhookUrl,
failedAt : new Date ().toISOString (),
retryCount : 0 ,
maxRetries : 3 ,
});
console .warn (`DLQ: Added row (${this .entries.length} total). Error: ${error} ` );
}
async retryAll (): Promise <{ retried : number ; succeeded : number ; permanentFailures : number }> {
let succeeded = 0 , permanentFailures = 0 ;
const remaining : DLQEntry [] = [];
for (const entry of this .entries ) {
if (entry.retryCount >= entry.maxRetries ) {
permanentFailures++;
continue ;
}
try {
const res = await fetch (entry.webhookUrl , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify (entry.row ),
});
if (res.ok ) {
succeeded++;
} else {
entry.retryCount ++;
remaining.push (entry);
}
} catch {
entry.retryCount ++;
remaining.push (entry);
}
await new Promise (r => setTimeout (r, 500 ));
}
this .entries = remaining;
return { retried : this .entries .length + succeeded + permanentFailures, succeeded, permanentFailures };
}
getStats ( ) {
return {
pending : this .entries .length ,
byError : this .entries .reduce ((acc, e ) => {
acc[e.error ] = (acc[e.error ] || 0 ) + 1 ;
return acc;
}, {} as Record <string , number >),
};
}
}
Step 3: Webhook Health Monitor
class WebhookHealthMonitor {
private successCount = 0 ;
private failureCount = 0 ;
private lastCheck : Date = new Date ();
private readonly windowMs = 5 * 60 * 1000 ;
record (success : boolean ) {
if (success) this .successCount ++;
else this .failureCount ++;
}
getHealthScore (): { score : number ; status : 'healthy' | 'degraded' | 'unhealthy' } {
const total = this .successCount + this .failureCount ;
if (total === 0 ) return { score : 100 , status : 'healthy' };
const score = (this .successCount / total) * 100 ;
if (Date .now () - this .lastCheck .getTime () > this .windowMs ) {
this .successCount = 0 ;
this .failureCount = 0 ;
this .lastCheck = new Date ();
}
return {
score,
status : score > 95 ? 'healthy' : score > 80 ? 'degraded' : 'unhealthy' ,
};
}
}
Step 4: Graceful Degradation When Clay Is Down
interface FallbackConfig {
cacheEnrichedData : boolean ;
queueForLater : boolean ;
useLocalFallback : boolean ;
}
class ClayWithFallback {
private cache = new Map <string , Record <string , unknown >>();
private offlineQueue : Record <string , unknown >[] = [];
async enrichOrFallback (
lead : Record <string , unknown >,
webhookUrl : string ,
config : FallbackConfig ,
): Promise <{ data : Record <string , unknown >; source : 'clay' | 'cache' | 'queued' | 'local' }> {
try {
const res = await fetch (webhookUrl, {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify (lead),
signal : AbortSignal .timeout (10_000 ),
});
if (res.ok ) {
return { data : lead, source : 'clay' };
}
} catch {
console .warn ('Clay webhook unavailable — using fallback' );
}
const domain = lead.domain as string ;
if (config.cacheEnrichedData && this .cache .has (domain)) {
return { data : { ...lead, ...this .cache .get (domain) }, source : 'cache' };
}
if (config.queueForLater ) {
this .offlineQueue .push (lead);
return { data : lead, source : 'queued' };
}
if (config.useLocalFallback ) {
return {
data : { ...lead, company_name : domain.replace (/\.\w+$/ , '' ).replace (/-/g , ' ' ) },
source : 'local' ,
};
}
return { data : lead, source : 'local' };
}
async drainOfflineQueue (webhookUrl : string ): Promise <number > {
let drained = 0 ;
while (this .offlineQueue .length > 0 ) {
const lead = this .offlineQueue .shift ()!;
try {
await fetch (webhookUrl, {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify (lead),
});
drained++;
await new Promise (r => setTimeout (r, 200 ));
} catch {
this .offlineQueue .unshift (lead);
break ;
}
}
return drained;
}
}
Step 5: Combine All Patterns
const circuitBreaker = new CreditCircuitBreaker (500 );
const dlq = new ClayDLQ ();
const healthMonitor = new WebhookHealthMonitor ();
async function reliableEnrich (lead : Record <string , unknown >, webhookUrl : string ): Promise <void > {
const { allowed, reason } = circuitBreaker.canProcess (6 );
if (!allowed) {
dlq.addToQueue (lead, `Circuit breaker: ${reason} ` , webhookUrl);
return ;
}
try {
const res = await fetch (webhookUrl, {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify (lead),
});
if (res.ok ) {
circuitBreaker.recordSuccess (6 );
healthMonitor.record (true );
} else {
throw new Error (`HTTP ${res.status} ` );
}
} catch (err) {
circuitBreaker.recordFailure ();
healthMonitor.record (false );
dlq.addToQueue (lead, (err as Error ).message , webhookUrl);
}
}
Error Handling Issue Cause Solution Runaway credit spend No budget circuit breaker Implement credit budget limiter Lost leads during outage No DLQ Queue failed submissions for retry Silent webhook failures No health monitoring Track success/failure rates Clay outage blocks pipeline No fallback Implement cache + queue fallback
Resources
Next Steps For policy guardrails, see clay-policy-guardrails.