Customer.io CI Integration

SkillDev tools

'Configure Customer.io CI/CD integration with automated testing.

Use Customer.io CI Integration in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Customer.io CI Integration and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Customer.io CI Integration skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Customer.io CI IntegrationStart free

What this skill tells your AI

The instructions your AI receives, as published by jeremylongshore/tons-of-skills-marketplace in skills/.curated/customerio-ci-integration/SKILL.md and read by Ahel’s review.

Output

  • A credential-free pull-request lane for schema/template/unit checks and a trusted scoped lane for optional integration tests.
  • A redacted CI receipt with validation results and a safe failure/retry procedure.

Examples

Run event-schema and template tests on every pull request using fixtures, then execute a single synthetic integration event only from a protected branch with a development workspace secret. If a live check fails, retain redacted status and back off; never expose workspace credentials to forked code or bypass protected checks.

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

# .github/workflows/customerio-tests.yml
name: Customer.io Integration Tests
on:
  push:
    paths:
      - "lib/customerio-*.ts"
      - "services/customerio-*.ts"
      - "tests/customerio*"
  pull_request:
    paths:
      - "lib/customerio-*.ts"
      - "services/customerio-*.ts"

env:
  CUSTOMERIO_SITE_ID: ${{ secrets.CIO_TEST_SITE_ID }}
  CUSTOMERIO_TRACK_API_KEY: ${{ secrets.CIO_TEST_TRACK_API_KEY }}
  CUSTOMERIO_APP_API_KEY: ${{ secrets.CIO_TEST_APP_API_KEY }}
  CUSTOMERIO_REGION: us

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npx vitest run tests/customerio --reporter=verbose
        env:
          CUSTOMERIO_DRY_RUN: "true"  # Unit tests use mocks

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests  # Only run if unit tests pass
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - name: Validate credentials
        run: |
          if [ -z "$CUSTOMERIO_SITE_ID" ]; then
            echo "::warning::CIO credentials not configured — skipping integration tests"
            exit 0
          fi
      - name: Run integration tests
        run: npx vitest run tests/customerio.integration --reporter=verbose
      - name: Cleanup test users
        if: always()
        run: npx tsx scripts/cio-cleanup-test-users.ts

Step 2: Test Fixtures and Helpers

// tests/helpers/cio-test-utils.ts
import { TrackClient, RegionUS } from "customerio-node";

const TEST_RUN_ID = `ci-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const createdUsers: string[] = [];

export function getCioTestClient(): TrackClient {
  return new TrackClient(
    process.env.CUSTOMERIO_SITE_ID!,
    process.env.CUSTOMERIO_TRACK_API_KEY!,
    { region: RegionUS }
  );
}

export function testUserId(label: string): string {
  const id = `${TEST_RUN_ID}-${label}`;
  createdUsers.push(id);
  return id;
}

export async function cleanupTestUsers(client: TrackClient): Promise<void> {
  console.log(`Cleaning up ${createdUsers.length} test users...`);
  for (const userId of createdUsers) {
    try {
      await client.suppress(userId);
      await client.destroy(userId);
    } catch {
      // Ignore cleanup errors
    }
  }
  createdUsers.length = 0;
}

Step 3: Integration Test Suite

// tests/customerio.integration.test.ts
import { describe, it, expect, afterAll } from "vitest";
import { getCioTestClient, testUserId, cleanupTestUsers } from "./helpers/cio-test-utils";

const cio = getCioTestClient();

describe("Customer.io Integration", () => {
  afterAll(async () => {
    await cleanupTestUsers(cio);
  });

  it("should identify a new user", async () => {
    const userId = testUserId("identify-new");
    await expect(
      cio.identify(userId, {
        email: `${userId}@test.example.com`,
        created_at: Math.floor(Date.now() / 1000),
      })
    ).resolves.not.toThrow();
  });

  it("should update an existing user", async () => {
    const userId = testUserId("identify-update");
    await cio.identify(userId, { email: `${userId}@test.example.com` });
    await expect(
      cio.identify(userId, { plan: "pro", updated: true })
    ).resolves.not.toThrow();
  });

  it("should track an event on a user", async () => {
    const userId = testUserId("track-event");
    await cio.identify(userId, { email: `${userId}@test.example.com` });
    await expect(
      cio.track(userId, {
        name: "ci_test_event",
        data: { test_run: true, timestamp: Date.now() },
      })
    ).resolves.not.toThrow();
  });

  it("should track an anonymous event", async () => {
    await expect(
      cio.trackAnonymous({
        anonymous_id: testUserId("anon"),
        name: "ci_anonymous_test",
        data: { page: "/test" },
      })
    ).resolves.not.toThrow();
  });

  it("should suppress a user", async () => {
    const userId = testUserId("suppress");
    await cio.identify(userId, { email: `${userId}@test.example.com` });
    await expect(cio.suppress(userId)).resolves.not.toThrow();
  });

  it("should reject invalid credentials", async () => {
    const badClient = new (await import("customerio-node")).TrackClient(
      "invalid", "invalid", { region: (await import("customerio-node")).RegionUS }
    );
    await expect(
      badClient.identify("x", { email: "x@test.com" })
    ).rejects.toThrow();
  });
});

Step 4: Test User Cleanup Script

// scripts/cio-cleanup-test-users.ts
import { TrackClient, RegionUS } from "customerio-node";

const cio = new TrackClient(
  process.env.CUSTOMERIO_SITE_ID!,
  process.env.CUSTOMERIO_TRACK_API_KEY!,
  { region: RegionUS }
);

// Clean up any test users from failed CI runs
// This uses the ci- prefix convention from testUserId()
async function cleanup() {
  console.log("Cleaning up CI test users...");
  console.log("Note: Customer.io doesn't have a list/search API via Track API.");
  console.log("Cleanup relies on suppress+destroy for known test user IDs.");
  console.log("For bulk cleanup, use the Customer.io dashboard People filter.");
}

cleanup();

Step 5: GitHub Secrets Setup

# Set up CI secrets (use a dedicated test workspace — NEVER production)
gh secret set CIO_TEST_SITE_ID --body "your-test-site-id"
gh secret set CIO_TEST_TRACK_API_KEY --body "your-test-track-key"
gh secret set CIO_TEST_APP_API_KEY --body "your-test-app-key"

Step 6: Pre-commit Hook

# .husky/pre-commit (or lint-staged config)
npx lint-staged
// package.json
{
  "lint-staged": {
    "lib/customerio-*.ts": ["eslint --fix", "vitest related --run"],
    "services/customerio-*.ts": ["eslint --fix", "vitest related --run"]
  }
}

CI Best Practices

PracticeRationale
Dedicated test workspacePrevents CI from polluting dev/staging data
Unique test user IDsPrevents collisions between parallel CI runs
Always cleanup in afterAllPrevents accumulating stale test profiles
Rate limit awarenessAdd small delays between batched API calls in CI
Skip integration tests if no credsPRs from forks won't have secrets

Error Handling

IssueSolution
Secrets not available in PRFork PRs don't get secrets — skip integration tests gracefully
Test user pollutionUse ${TEST_RUN_ID} prefix, cleanup in afterAll
Rate limiting in CIKeep integration test count under 50 API calls
Flaky network failuresAdd retry logic to integration tests

Resources

Next Steps

After CI setup, proceed to customerio-deploy-pipeline for production deployment.

Signals

GitHub stars
3k
Forks
415
Last commit
Oct 2026
Advanced
Item type
skill
Key
customerio-ci-integration
Source
github.com/jeremylongshore/tons-of-skills-marketplace