From klaviyo-pack
Configures Klaviyo API for dev, staging, and production with per-env keys, NODE_ENV detection, secret management via AWS/GCP/Vault, and non-prod safeguards.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin klaviyo-packThis skill is limited to using the following tools:
Configure Klaviyo across development, staging, and production with separate API keys, environment detection, secret management, and production safeguards.
Executes Klaviyo production checklist for deploying integrations: verifies auth/secrets, API calls with klaviyo-api SDK, error resilience, webhook security, monitoring, and health checks.
Configures Customer.io multi-environment setups with isolated workspaces for dev/staging/prod, TypeScript configs, validation, Kubernetes overlays, and data isolation.
Integrates Klaviyo email/SMS marketing API: manage profiles, track events, build flows, and segment customers. Use for e-commerce marketing features with Node.js, Python SDKs or direct HTTP.
Share bugs, ideas, or general feedback.
Configure Klaviyo across development, staging, and production with separate API keys, environment detection, secret management, and production safeguards.
klaviyo-api SDK installed| Environment | Klaviyo Account | API Key | Use Case |
|---|---|---|---|
| Development | Test account | pk_test_dev_*** | Local development, exploration |
| Staging | Test account | pk_test_staging_*** | Pre-prod validation, integration tests |
| Production | Production account | pk_live_*** | Real customer data, live sends |
Important: Klaviyo does not have a sandbox mode. Use a separate test account for dev/staging to avoid sending real emails.
// src/config/klaviyo.ts
import { ApiKeySession } from 'klaviyo-api';
type Environment = 'development' | 'staging' | 'production';
interface KlaviyoEnvConfig {
privateKey: string;
revision: string;
webhookSecret: string;
enableSending: boolean;
rateLimitConcurrency: number;
cacheEnabled: boolean;
cacheTtlMs: number;
}
function detectEnvironment(): Environment {
const env = process.env.NODE_ENV || 'development';
if (['production', 'staging'].includes(env)) return env as Environment;
return 'development';
}
const ENV_CONFIGS: Record<Environment, Partial<KlaviyoEnvConfig>> = {
development: {
enableSending: false, // Never send real emails from dev
rateLimitConcurrency: 5,
cacheEnabled: false,
cacheTtlMs: 0,
},
staging: {
enableSending: false, // Only send to test addresses
rateLimitConcurrency: 10,
cacheEnabled: true,
cacheTtlMs: 60_000,
},
production: {
enableSending: true,
rateLimitConcurrency: 20,
cacheEnabled: true,
cacheTtlMs: 300_000,
},
};
export function getKlaviyoConfig(): KlaviyoEnvConfig {
const env = detectEnvironment();
const envConfig = ENV_CONFIGS[env];
return {
privateKey: process.env.KLAVIYO_PRIVATE_KEY || '',
revision: '2024-10-15',
webhookSecret: process.env.KLAVIYO_WEBHOOK_SIGNING_SECRET || '',
enableSending: false,
rateLimitConcurrency: 10,
cacheEnabled: true,
cacheTtlMs: 60_000,
...envConfig,
};
}
export function getSession(): ApiKeySession {
const config = getKlaviyoConfig();
if (!config.privateKey) throw new Error(`KLAVIYO_PRIVATE_KEY not set for ${detectEnvironment()}`);
return new ApiKeySession(config.privateKey);
}
# Create secrets per environment
echo -n "pk_test_dev_***" | gcloud secrets create klaviyo-key-dev --data-file=-
echo -n "pk_test_staging_***" | gcloud secrets create klaviyo-key-staging --data-file=-
echo -n "pk_live_***" | gcloud secrets create klaviyo-key-prod --data-file=-
# Access in Cloud Run
gcloud run deploy my-app \
--set-secrets=KLAVIYO_PRIVATE_KEY=klaviyo-key-prod:latest
aws secretsmanager create-secret \
--name klaviyo/production/private-key \
--secret-string "pk_live_***"
aws secretsmanager create-secret \
--name klaviyo/staging/private-key \
--secret-string "pk_test_staging_***"
// Load from AWS at startup
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
async function loadKlaviyoKey(env: string): Promise<string> {
const client = new SecretsManager();
const result = await client.getSecretValue({ SecretId: `klaviyo/${env}/private-key` });
return result.SecretString!;
}
# .env.local (git-ignored)
KLAVIYO_PRIVATE_KEY=pk_test_dev_********************************
KLAVIYO_PUBLIC_KEY=DevKey
KLAVIYO_WEBHOOK_SIGNING_SECRET=whsec_test_***
NODE_ENV=development
// src/klaviyo/guards.ts
/** Prevent dangerous operations in non-production environments */
export function requireProduction(operation: string): void {
const env = process.env.NODE_ENV;
if (env !== 'production') {
throw new Error(`[Klaviyo Guard] ${operation} is only allowed in production (current: ${env})`);
}
}
/** Prevent campaign sends in non-production */
export function guardCampaignSend(): void {
const config = getKlaviyoConfig();
if (!config.enableSending) {
throw new Error('[Klaviyo Guard] Campaign sending is disabled in this environment');
}
}
/** Restrict deletion operations */
export async function guardedProfileDeletion(profileId: string): Promise<void> {
const env = process.env.NODE_ENV;
// In dev/staging, just log instead of deleting
if (env !== 'production') {
console.log(`[Klaviyo Guard] Would delete profile ${profileId} (skipped in ${env})`);
return;
}
// In production, proceed with actual deletion
const dataPrivacyApi = new DataPrivacyApi(getSession());
await dataPrivacyApi.requestProfileDeletion({
data: {
type: 'data-privacy-deletion-job',
attributes: { profile: { data: { type: 'profile', id: profileId } } },
},
});
}
name: Deploy
on:
push:
branches: [main, staging]
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- branch: staging
env: staging
secret_key: KLAVIYO_KEY_STAGING
- branch: main
env: production
secret_key: KLAVIYO_KEY_PROD
if: github.ref == format('refs/heads/{0}', matrix.branch)
env:
NODE_ENV: ${{ matrix.env }}
KLAVIYO_PRIVATE_KEY: ${{ secrets[matrix.secret_key] }}
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
- name: Verify Klaviyo connectivity
run: |
curl -s -w "HTTP %{http_code}" -o /dev/null \
-H "Authorization: Klaviyo-API-Key $KLAVIYO_PRIVATE_KEY" \
-H "revision: 2024-10-15" \
"https://a.klaviyo.com/api/accounts/"
- run: npm run deploy:${{ matrix.env }}
// src/startup.ts
import { AccountsApi } from 'klaviyo-api';
import { getSession, getKlaviyoConfig } from './config/klaviyo';
export async function validateKlaviyoEnvironment(): Promise<void> {
const config = getKlaviyoConfig();
console.log(`[Klaviyo] Environment: ${process.env.NODE_ENV}`);
console.log(`[Klaviyo] Sending enabled: ${config.enableSending}`);
console.log(`[Klaviyo] Cache: ${config.cacheEnabled ? `enabled (${config.cacheTtlMs}ms)` : 'disabled'}`);
try {
const api = new AccountsApi(getSession());
const accounts = await api.getAccounts();
const name = accounts.body.data[0].attributes.contactInformation?.organizationName;
console.log(`[Klaviyo] Connected to: ${name}`);
// Warn if production key is used in non-production
if (process.env.NODE_ENV !== 'production' && config.privateKey.includes('live')) {
console.error('[Klaviyo] WARNING: Production API key detected in non-production environment!');
}
} catch (error: any) {
console.error(`[Klaviyo] Connection failed: ${error.status} ${error.message}`);
if (process.env.NODE_ENV === 'production') process.exit(1);
}
}
| Issue | Cause | Solution |
|---|---|---|
| Wrong key for environment | Missing env-specific secret | Verify secret per environment |
| Production key in dev | Key leak risk | Add startup validation warning |
| Sends in staging | enableSending not checked | Add campaign send guard |
| Config merge fails | Invalid env value | Validate NODE_ENV on startup |
For observability setup, see klaviyo-observability.