Claude-code-plugins klaviyo-local-dev-loop
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/klaviyo-pack/skills/klaviyo-local-dev-loop" ~/.claude/skills/jeremylongshore-claude-code-plugins-klaviyo-local-dev-loop && rm -rf "$T"
manifest:
plugins/saas-packs/klaviyo-pack/skills/klaviyo-local-dev-loop/SKILL.mdsource content
Klaviyo Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for Klaviyo integrations with hot reload, SDK mocking, and integration tests.
Prerequisites
- Completed
setupklaviyo-install-auth - Node.js 18+ with npm/pnpm
package installedklaviyo-api
Instructions
Step 1: Project Structure
my-klaviyo-project/ ├── src/ │ ├── klaviyo/ │ │ ├── client.ts # ApiKeySession + API class singletons │ │ ├── profiles.ts # Profile operations │ │ ├── events.ts # Event tracking │ │ └── lists.ts # List management │ └── index.ts ├── tests/ │ ├── unit/ │ │ └── profiles.test.ts # Mocked SDK tests │ └── integration/ │ └── klaviyo.test.ts # Live API tests (CI only) ├── .env.local # Local secrets (git-ignored) ├── .env.example # Template for team ├── .env.test # Test environment (sandbox key) └── package.json
Step 2: Environment Configuration
# .env.example (commit this) KLAVIYO_PRIVATE_KEY=pk_your_test_key_here KLAVIYO_PUBLIC_KEY=YourPublicKey NODE_ENV=development # .env.local (git-ignored, actual secrets) KLAVIYO_PRIVATE_KEY=pk_********************************
// package.json scripts { "scripts": { "dev": "tsx watch src/index.ts", "test": "vitest", "test:watch": "vitest --watch", "test:integration": "KLAVIYO_TEST=1 vitest --config vitest.integration.config.ts", "typecheck": "tsc --noEmit" } }
Step 3: SDK Client Singleton
// src/klaviyo/client.ts import { ApiKeySession, ProfilesApi, EventsApi, ListsApi, SegmentsApi, CampaignsApi, MetricsApi, } from 'klaviyo-api'; let session: ApiKeySession | null = null; function getSession(): ApiKeySession { if (!session) { const key = process.env.KLAVIYO_PRIVATE_KEY; if (!key) throw new Error('KLAVIYO_PRIVATE_KEY not set'); session = new ApiKeySession(key); } return session; } // Lazy singletons -- only instantiate what you use export const profiles = () => new ProfilesApi(getSession()); export const events = () => new EventsApi(getSession()); export const lists = () => new ListsApi(getSession()); export const segments = () => new SegmentsApi(getSession()); export const campaigns = () => new CampaignsApi(getSession()); export const metrics = () => new MetricsApi(getSession());
Step 4: Unit Testing with Mocks
// tests/unit/profiles.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock the entire klaviyo-api module vi.mock('klaviyo-api', () => ({ ApiKeySession: vi.fn(), ProfilesApi: vi.fn().mockImplementation(() => ({ createProfile: vi.fn().mockResolvedValue({ body: { data: { id: '01JMOCKPROFILEID', type: 'profile', attributes: { email: 'test@example.com', firstName: 'Test' }, }, }, }), getProfiles: vi.fn().mockResolvedValue({ body: { data: [{ id: '01JMOCKPROFILEID', attributes: { email: 'test@example.com' } }], links: { next: null }, }, }), })), ProfileEnum: { Profile: 'profile' }, })); import { ProfilesApi, ApiKeySession } from 'klaviyo-api'; describe('Profile operations', () => { let profilesApi: ProfilesApi; beforeEach(() => { const session = new ApiKeySession('pk_test_key'); profilesApi = new ProfilesApi(session); }); it('creates a profile with email', async () => { const result = await profilesApi.createProfile({ data: { type: 'profile' as any, attributes: { email: 'test@example.com', firstName: 'Test' }, }, }); expect(result.body.data.id).toBe('01JMOCKPROFILEID'); }); });
Step 5: Integration Test (runs against live API)
// tests/integration/klaviyo.test.ts import { describe, it, expect } from 'vitest'; import { ApiKeySession, ProfilesApi, AccountsApi } from 'klaviyo-api'; const SKIP = !process.env.KLAVIYO_TEST; describe.skipIf(SKIP)('Klaviyo Integration', () => { const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!); it('connects to Klaviyo account', async () => { const accountsApi = new AccountsApi(session); const result = await accountsApi.getAccounts(); expect(result.body.data).toHaveLength(1); expect(result.body.data[0].id).toBeTruthy(); }); it('creates and retrieves a test profile', async () => { const profilesApi = new ProfilesApi(session); const testEmail = `test-${Date.now()}@example.com`; await profilesApi.createProfile({ data: { type: 'profile' as any, attributes: { email: testEmail, firstName: 'IntegrationTest' }, }, }); const profiles = await profilesApi.getProfiles({ filter: `equals(email,"${testEmail}")`, }); expect(profiles.body.data[0].attributes.firstName).toBe('IntegrationTest'); }); });
Step 6: Hot Reload Development
# Start dev server with file watching npm run dev # In another terminal, run tests on change npm run test:watch
Output
- Working dev environment with hot reload via
tsx watch - Unit tests with mocked
SDKklaviyo-api - Integration tests gated behind
KLAVIYO_TEST=1 - Client singleton pattern for consistent SDK usage
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Missing .env.local | Copy from .env.example |
| Mock type errors | SDK type mismatches | Use for mock enum values |
| Integration test 429 | Rate limited in CI | Add delays between tests or use test key |
not found | Missing dependency | |
Resources
Next Steps
See
klaviyo-sdk-patterns for production-ready code patterns.