Customer.io CI Integration Overview Set up CI/CD pipelines for Customer.io integrations: GitHub Actions workflow with unit + integration tests, test fixtures with automatic cleanup, pre-commit hooks, and environment-specific credential management. Prerequisites - GitHub repository with Node.js project - Separate Customer.io workspace for CI testing (do NOT use production) - GitHub Actions secrets configured Instructions Step 1: GitHub Actions Workflow Step 2: Test Fixtures and Helpers Step 3: Integration Test Suite Step 4: Test User Cleanup Script Step 5: GitHub Secrets Setup Step 6: Pre-comm…

\n pass_filenames: false\n\n - id: customerio-types\n name: Type check Customer.io\n entry: npm run typecheck:customerio\n language: system\n files: 'src/lib/customerio/.*\\.ts

Customer.io CI Integration Overview Set up CI/CD pipelines for Customer.io integrations: GitHub Actions workflow with unit + integration tests, test fixtures with automatic cleanup, pre-commit hooks, and environment-specific credential management. Prerequisites - GitHub repository with Node.js project - Separate Customer.io workspace for CI testing (do NOT use production) - GitHub Actions secrets configured Instructions Step 1: GitHub Actions Workflow Step 2: Test Fixtures and Helpers Step 3: Integration Test Suite Step 4: Test User Cleanup Script Step 5: GitHub Secrets Setup Step 6: Pre-comm…

\n pass_filenames: false\n```\n\n### Step 6: Environment Management\n\n```typescript\n// scripts/setup-ci-environment.ts\nimport { execSync } from 'child_process';\n\ninterface CIEnvironment {\n name: string;\n siteId: string;\n apiKey: string;\n}\n\nconst environments: Record\u003cstring, CIEnvironment> = {\n testing: {\n name: 'testing',\n siteId: process.env.CUSTOMERIO_TEST_SITE_ID!,\n apiKey: process.env.CUSTOMERIO_TEST_API_KEY!\n },\n staging: {\n name: 'staging',\n siteId: process.env.CUSTOMERIO_STAGING_SITE_ID!,\n apiKey: process.env.CUSTOMERIO_STAGING_API_KEY!\n },\n production: {\n name: 'production',\n siteId: process.env.CUSTOMERIO_PROD_SITE_ID!,\n apiKey: process.env.CUSTOMERIO_PROD_API_KEY!\n }\n};\n\nfunction validateEnvironment(env: string): void {\n const config = environments[env];\n if (!config) {\n throw new Error(`Unknown environment: ${env}`);\n }\n if (!config.siteId || !config.apiKey) {\n throw new Error(`Missing credentials for environment: ${env}`);\n }\n console.log(`Environment ${env} validated`);\n}\n\n// Validate on CI startup\nconst targetEnv = process.env.CI_ENVIRONMENT || 'testing';\nvalidateEnvironment(targetEnv);\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7548,"content_sha256":"ee72ee72c162342ec7b8a5f635e4012d8d9ed9e43d92fc0fdf86fb8994dcd83b"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Customer.io CI Integration","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Set up CI/CD pipelines for Customer.io integrations: GitHub Actions workflow with unit + integration tests, test fixtures with automatic cleanup, pre-commit hooks, and environment-specific credential management.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub repository with Node.js project","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Separate Customer.io workspace for CI testing (do NOT use production)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub Actions secrets configured","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Instructions","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: GitHub Actions Workflow","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# .github/workflows/customerio-tests.yml\nname: Customer.io Integration Tests\non:\n push:\n paths:\n - \"lib/customerio-*.ts\"\n - \"services/customerio-*.ts\"\n - \"tests/customerio*\"\n pull_request:\n paths:\n - \"lib/customerio-*.ts\"\n - \"services/customerio-*.ts\"\n\nenv:\n CUSTOMERIO_SITE_ID: ${{ secrets.CIO_TEST_SITE_ID }}\n CUSTOMERIO_TRACK_API_KEY: ${{ secrets.CIO_TEST_TRACK_API_KEY }}\n CUSTOMERIO_APP_API_KEY: ${{ secrets.CIO_TEST_APP_API_KEY }}\n CUSTOMERIO_REGION: us\n\njobs:\n unit-tests:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n cache: npm\n - run: npm ci\n - run: npx vitest run tests/customerio --reporter=verbose\n env:\n CUSTOMERIO_DRY_RUN: \"true\" # Unit tests use mocks\n\n integration-tests:\n runs-on: ubuntu-latest\n needs: unit-tests # Only run if unit tests pass\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n cache: npm\n - run: npm ci\n - name: Validate credentials\n run: |\n if [ -z \"$CUSTOMERIO_SITE_ID\" ]; then\n echo \"::warning::CIO credentials not configured — skipping integration tests\"\n exit 0\n fi\n - name: Run integration tests\n run: npx vitest run tests/customerio.integration --reporter=verbose\n - name: Cleanup test users\n if: always()\n run: npx tsx scripts/cio-cleanup-test-users.ts","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Test Fixtures and Helpers","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// tests/helpers/cio-test-utils.ts\nimport { TrackClient, RegionUS } from \"customerio-node\";\n\nconst TEST_RUN_ID = `ci-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\nconst createdUsers: string[] = [];\n\nexport function getCioTestClient(): TrackClient {\n return new TrackClient(\n process.env.CUSTOMERIO_SITE_ID!,\n process.env.CUSTOMERIO_TRACK_API_KEY!,\n { region: RegionUS }\n );\n}\n\nexport function testUserId(label: string): string {\n const id = `${TEST_RUN_ID}-${label}`;\n createdUsers.push(id);\n return id;\n}\n\nexport async function cleanupTestUsers(client: TrackClient): Promise\u003cvoid> {\n console.log(`Cleaning up ${createdUsers.length} test users...`);\n for (const userId of createdUsers) {\n try {\n await client.suppress(userId);\n await client.destroy(userId);\n } catch {\n // Ignore cleanup errors\n }\n }\n createdUsers.length = 0;\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Integration Test Suite","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// tests/customerio.integration.test.ts\nimport { describe, it, expect, afterAll } from \"vitest\";\nimport { getCioTestClient, testUserId, cleanupTestUsers } from \"./helpers/cio-test-utils\";\n\nconst cio = getCioTestClient();\n\ndescribe(\"Customer.io Integration\", () => {\n afterAll(async () => {\n await cleanupTestUsers(cio);\n });\n\n it(\"should identify a new user\", async () => {\n const userId = testUserId(\"identify-new\");\n await expect(\n cio.identify(userId, {\n email: `${userId}@test.example.com`,\n created_at: Math.floor(Date.now() / 1000),\n })\n ).resolves.not.toThrow();\n });\n\n it(\"should update an existing user\", async () => {\n const userId = testUserId(\"identify-update\");\n await cio.identify(userId, { email: `${userId}@test.example.com` });\n await expect(\n cio.identify(userId, { plan: \"pro\", updated: true })\n ).resolves.not.toThrow();\n });\n\n it(\"should track an event on a user\", async () => {\n const userId = testUserId(\"track-event\");\n await cio.identify(userId, { email: `${userId}@test.example.com` });\n await expect(\n cio.track(userId, {\n name: \"ci_test_event\",\n data: { test_run: true, timestamp: Date.now() },\n })\n ).resolves.not.toThrow();\n });\n\n it(\"should track an anonymous event\", async () => {\n await expect(\n cio.trackAnonymous({\n anonymous_id: testUserId(\"anon\"),\n name: \"ci_anonymous_test\",\n data: { page: \"/test\" },\n })\n ).resolves.not.toThrow();\n });\n\n it(\"should suppress a user\", async () => {\n const userId = testUserId(\"suppress\");\n await cio.identify(userId, { email: `${userId}@test.example.com` });\n await expect(cio.suppress(userId)).resolves.not.toThrow();\n });\n\n it(\"should reject invalid credentials\", async () => {\n const badClient = new (await import(\"customerio-node\")).TrackClient(\n \"invalid\", \"invalid\", { region: (await import(\"customerio-node\")).RegionUS }\n );\n await expect(\n badClient.identify(\"x\", { email: \"[email protected]\" })\n ).rejects.toThrow();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Test User Cleanup Script","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// scripts/cio-cleanup-test-users.ts\nimport { TrackClient, RegionUS } from \"customerio-node\";\n\nconst cio = new TrackClient(\n process.env.CUSTOMERIO_SITE_ID!,\n process.env.CUSTOMERIO_TRACK_API_KEY!,\n { region: RegionUS }\n);\n\n// Clean up any test users from failed CI runs\n// This uses the ci- prefix convention from testUserId()\nasync function cleanup() {\n console.log(\"Cleaning up CI test users...\");\n console.log(\"Note: Customer.io doesn't have a list/search API via Track API.\");\n console.log(\"Cleanup relies on suppress+destroy for known test user IDs.\");\n console.log(\"For bulk cleanup, use the Customer.io dashboard People filter.\");\n}\n\ncleanup();","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: GitHub Secrets Setup","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Set up CI secrets (use a dedicated test workspace — NEVER production)\ngh secret set CIO_TEST_SITE_ID --body \"your-test-site-id\"\ngh secret set CIO_TEST_TRACK_API_KEY --body \"your-test-track-key\"\ngh secret set CIO_TEST_APP_API_KEY --body \"your-test-app-key\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 6: Pre-commit Hook","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# .husky/pre-commit (or lint-staged config)\nnpx lint-staged","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"// package.json\n{\n \"lint-staged\": {\n \"lib/customerio-*.ts\": [\"eslint --fix\", \"vitest related --run\"],\n \"services/customerio-*.ts\": [\"eslint --fix\", \"vitest related --run\"]\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"CI Best Practices","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Practice","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rationale","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dedicated test workspace","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevents CI from polluting dev/staging data","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unique test user IDs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevents collisions between parallel CI runs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Always cleanup in ","type":"text"},{"text":"afterAll","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevents accumulating stale test profiles","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rate limit awareness","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add small delays between batched API calls in CI","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Skip integration tests if no creds","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PRs from forks won't have secrets","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Issue","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secrets not available in PR","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fork PRs don't get secrets — skip integration tests gracefully","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test user pollution","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"${TEST_RUN_ID}","type":"text","marks":[{"type":"code_inline"}]},{"text":" prefix, cleanup in ","type":"text"},{"text":"afterAll","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rate limiting in CI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Keep integration test count under 50 API calls","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flaky network failures","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add retry logic to integration tests","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub Actions Encrypted Secrets","type":"text","marks":[{"type":"link","attrs":{"href":"https://docs.github.com/en/actions/security-guides/encrypted-secrets","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"vitest Documentation","type":"text","marks":[{"type":"link","attrs":{"href":"https://vitest.dev/","title":null}}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Next Steps","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"After CI setup, proceed to ","type":"text"},{"text":"customerio-deploy-pipeline","type":"text","marks":[{"type":"code_inline"}]},{"text":" for production deployment.","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"customerio-ci-integration","tags":["saas","customer-io","ci-cd","testing","github-actions"],"author":"@skillopedia","source":{"stars":2275,"repo_name":"claude-code-plugins-plus-skills","origin_url":"https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/saas-packs/customerio-pack/skills/customerio-ci-integration/SKILL.md","repo_owner":"jeremylongshore","body_sha256":"18ebbb9b1e4f4dbcfa9e537564686243bc3d3b95a22744ee223d750f915eaefb","cluster_key":"a9b412a7aad021d121de8fd4d1fc6289cb71dbd49da23bd9c9505738f68bddab","clean_bundle":{"format":"clean-skill-bundle-v1","source":"jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/customerio-pack/skills/customerio-ci-integration/SKILL.md","attachments":[{"id":"3e87572a-f667-51d2-8fcf-20f0252f0862","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e87572a-f667-51d2-8fcf-20f0252f0862/attachment.md","path":"references/implementation-guide.md","size":7548,"sha256":"ee72ee72c162342ec7b8a5f635e4012d8d9ed9e43d92fc0fdf86fb8994dcd83b","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"43c3ea63c2117f7572eedf72b01799def317b99267bc0aa345a0cd5f634c92b8","attachment_count":1,"text_attachments":1,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/saas-packs/customerio-pack/skills/customerio-ci-integration/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"testing-qa","import_tag":"clean-skills-v1","description":"Configure Customer.io CI/CD integration with automated testing.\nUse when setting up GitHub Actions, integration test suites,\nor pre-commit validation for Customer.io code.\nTrigger: \"customer.io ci\", \"customer.io github actions\",\n\"customer.io pipeline\", \"customer.io automated testing\".\n","allowed-tools":"Read, Write, Edit, Bash(npm:*), Bash(gh:*), Glob, Grep","compatibility":"Designed for Claude Code, also compatible with Codex and OpenClaw"}},"renderedAt":1782988169307}

Customer.io CI Integration Overview Set up CI/CD pipelines for Customer.io integrations: GitHub Actions workflow with unit + integration tests, test fixtures with automatic cleanup, pre-commit hooks, and environment-specific credential management. Prerequisites - GitHub repository with Node.js project - Separate Customer.io workspace for CI testing (do NOT use production) - GitHub Actions secrets configured Instructions Step 1: GitHub Actions Workflow Step 2: Test Fixtures and Helpers Step 3: Integration Test Suite Step 4: Test User Cleanup Script Step 5: GitHub Secrets Setup Step 6: Pre-comm…