Klaviyo Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for Klaviyo integrations with hot reload, SDK mocking, and integration tests. The loop keeps three concerns separate: a lazily-instantiated SDK client singleton, mocked unit tests that never hit the network, and live integration tests gated behind an opt-in flag so they only run in CI.
Prerequisites
- Completed
klaviyo-install-auth setup (provides your private API key)
- Node.js 18+ with
npm or pnpm on the PATH
klaviyo-api package installed as a project dependency
tsx and vitest installed as dev dependencies for hot reload and tests
Instructions
Follow six steps to stand up the loop. The full file contents for each step —
project layout, .env templates, package.json scripts, and the client
singleton — live in full walkthrough. The
complete test files live in test examples.
Project structure — create src/klaviyo/ for SDK modules and tests/{unit,integration}/. Keep secrets in a git-ignored .env.local, ship a committed .env.example.
Environment configuration — define KLAVIYO_PRIVATE_KEY / KLAVIYO_PUBLIC_KEY and wire the dev, test, test:watch, test:integration, and typecheck scripts.
SDK client singleton — read the key once, cache the ApiKeySession, and export lazy per-API accessors so you only instantiate what you use:
// src/klaviyo/client.ts
import { ApiKeySession, ProfilesApi } 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;
}
export const profiles = () => new ProfilesApi(getSession());
Unit testing with mocks — vi.mock('klaviyo-api', ...) the whole SDK so unit tests are deterministic and offline. See test examples.
Integration test — a describe.skipIf(!process.env.KLAVIYO_TEST) suite that exercises the live account. See test examples.
Hot reload development — run npm run dev (tsx watch) in one terminal and npm run test:watch in another for a tight edit-test cycle.
Output
- Working dev environment with hot reload via
tsx watch
- Unit tests with mocked
klaviyo-api SDK
- Integration tests gated behind
KLAVIYO_TEST=1
- Client singleton pattern for consistent SDK usage
Error Handling
| Error |
Cause |
Solution |
KLAVIYO_PRIVATE_KEY not set |
Missing .env.local |
Copy from .env.example |
| Mock type errors |
SDK type mismatches |
Use as any for mock enum values |
| Integration test 429 |
Rate limited in CI |
Add delays between tests or use test key |
tsx not found |
Missing dependency |
npm install -D tsx |
Examples
A minimal mocked unit test — no network, fully deterministic. The full unit and
integration suites are in test examples.
// tests/unit/profiles.test.ts
import { describe, it, expect, vi } from 'vitest';
vi.mock('klaviyo-api', () => ({
ApiKeySession: vi.fn(),
ProfilesApi: vi.fn().mockImplementation(() => ({
createProfile: vi.fn().mockResolvedValue({
body: { data: { id: '01JMOCKPROFILEID', attributes: { email: 'test@example.com' } } },
}),
})),
}));
import { ProfilesApi, ApiKeySession } from 'klaviyo-api';
describe('Profile operations', () => {
it('creates a profile with email', async () => {
const api = new ProfilesApi(new ApiKeySession('pk_test_key'));
const result = await api.createProfile({
data: { type: 'profile' as any, attributes: { email: 'test@example.com' } },
});
expect(result.body.data.id).toBe('01JMOCKPROFILEID');
});
});
Run it with npm run test, or npm run test:watch for the hot loop. To exercise
the live API instead, set KLAVIYO_TEST=1 and run npm run test:integration.
Resources
Next Steps
See klaviyo-sdk-patterns for production-ready code patterns, and
klaviyo-install-auth if you still need to provision an API key. Once the loop
is green locally, wire npm run test:integration into CI behind the
KLAVIYO_TEST gate so live checks run only where a sandbox key is available.
Source: jeremylongshore/claude-code-plugins-plus-skills → plugins/saas-packs/klaviyo-pack/skills/klaviyo-local-dev-loop/SKILL.md
1---2name: klaviyo-local-dev-loop3description: "'Configure Klaviyo local development with hot reload, mocking, and testing. Use when setting up a development environment, configuring test workflows, or establishing a fast iteration cycle with the Klaviyo API. Trigger with phrases like \"klaviyo dev setup\", \"klaviyo local development\", \"klaviyo dev environment\", \"develop with klaviyo\", \"klaviyo testing\". '"4allowed-tools: "Read, Write, Edit, Bash(npm:*), Bash(pnpm:*), Bash(npx:*), Grep"5category: testing-and-qa6source_repo: jeremylongshore/claude-code-plugins-plus-skills7source_path: "plugins/saas-packs/klaviyo-pack/skills/klaviyo-local-dev-loop/SKILL.md"8source_url: https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/saas-packs/klaviyo-pack/skills/klaviyo-local-dev-loop/SKILL.md9---1011# Klaviyo Local Dev Loop1213## Overview1415Set up a fast, reproducible local development workflow for Klaviyo integrations with hot reload, SDK mocking, and integration tests. The loop keeps three concerns separate: a lazily-instantiated SDK client singleton, mocked unit tests that never hit the network, and live integration tests gated behind an opt-in flag so they only run in CI.1617## Prerequisites1819- Completed `klaviyo-install-auth` setup (provides your private API key)20- Node.js 18+ with `npm` or `pnpm` on the PATH21- `klaviyo-api` package installed as a project dependency22- `tsx` and `vitest` installed as dev dependencies for hot reload and tests2324## Instructions2526Follow six steps to stand up the loop. The full file contents for each step —27project layout, `.env` templates, `package.json` scripts, and the client28singleton — live in [full walkthrough](references/implementation.md). The29complete test files live in [test examples](references/examples.md).30311. **Project structure** — create `src/klaviyo/` for SDK modules and `tests/{unit,integration}/`. Keep secrets in a git-ignored `.env.local`, ship a committed `.env.example`.322. **Environment configuration** — define `KLAVIYO_PRIVATE_KEY` / `KLAVIYO_PUBLIC_KEY` and wire the `dev`, `test`, `test:watch`, `test:integration`, and `typecheck` scripts.333. **SDK client singleton** — read the key once, cache the `ApiKeySession`, and export lazy per-API accessors so you only instantiate what you use:3435 ```typescript36 // src/klaviyo/client.ts37 import { ApiKeySession, ProfilesApi } from 'klaviyo-api';3839 let session: ApiKeySession | null = null;40 function getSession(): ApiKeySession {41 if (!session) {42 const key = process.env.KLAVIYO_PRIVATE_KEY;43 if (!key) throw new Error('KLAVIYO_PRIVATE_KEY not set');44 session = new ApiKeySession(key);45 }46 return session;47 }48 export const profiles = () => new ProfilesApi(getSession());49 ```50514. **Unit testing with mocks** — `vi.mock('klaviyo-api', ...)` the whole SDK so unit tests are deterministic and offline. See [test examples](references/examples.md).525. **Integration test** — a `describe.skipIf(!process.env.KLAVIYO_TEST)` suite that exercises the live account. See [test examples](references/examples.md).536. **Hot reload development** — run `npm run dev` (`tsx watch`) in one terminal and `npm run test:watch` in another for a tight edit-test cycle.5455## Output5657- Working dev environment with hot reload via `tsx watch`58- Unit tests with mocked `klaviyo-api` SDK59- Integration tests gated behind `KLAVIYO_TEST=1`60- Client singleton pattern for consistent SDK usage6162## Error Handling6364| Error | Cause | Solution |65|-------|-------|----------|66| `KLAVIYO_PRIVATE_KEY not set` | Missing .env.local | Copy from .env.example |67| Mock type errors | SDK type mismatches | Use `as any` for mock enum values |68| Integration test 429 | Rate limited in CI | Add delays between tests or use test key |69| `tsx` not found | Missing dependency | `npm install -D tsx` |7071## Examples7273A minimal mocked unit test — no network, fully deterministic. The full unit and74integration suites are in [test examples](references/examples.md).7576```typescript77// tests/unit/profiles.test.ts78import { describe, it, expect, vi } from 'vitest';7980vi.mock('klaviyo-api', () => ({81 ApiKeySession: vi.fn(),82 ProfilesApi: vi.fn().mockImplementation(() => ({83 createProfile: vi.fn().mockResolvedValue({84 body: { data: { id: '01JMOCKPROFILEID', attributes: { email: 'test@example.com' } } },85 }),86 })),87}));8889import { ProfilesApi, ApiKeySession } from 'klaviyo-api';9091describe('Profile operations', () => {92 it('creates a profile with email', async () => {93 const api = new ProfilesApi(new ApiKeySession('pk_test_key'));94 const result = await api.createProfile({95 data: { type: 'profile' as any, attributes: { email: 'test@example.com' } },96 });97 expect(result.body.data.id).toBe('01JMOCKPROFILEID');98 });99});100```101102Run it with `npm run test`, or `npm run test:watch` for the hot loop. To exercise103the live API instead, set `KLAVIYO_TEST=1` and run `npm run test:integration`.104105## Resources106107- [klaviyo-api-node SDK](https://github.com/klaviyo/klaviyo-api-node)108- [Vitest Documentation](https://vitest.dev/)109- [tsx (TypeScript Execute)](https://github.com/privatenumber/tsx)110- [Full implementation walkthrough](references/implementation.md)111- [Complete test examples](references/examples.md)112113## Next Steps114115See `klaviyo-sdk-patterns` for production-ready code patterns, and116`klaviyo-install-auth` if you still need to provision an API key. Once the loop117is green locally, wire `npm run test:integration` into CI behind the118`KLAVIYO_TEST` gate so live checks run only where a sandbox key is available.119120---121122**Source:** [`jeremylongshore/claude-code-plugins-plus-skills`](https://github.com/jeremylongshore/claude-code-plugins-plus-skills) → `plugins/saas-packs/klaviyo-pack/skills/klaviyo-local-dev-loop/SKILL.md`