Agent Skills: Customer.io Hello World

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/customerio-hello-world

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/customerio-pack/skills/customerio-hello-world

Skill Files

Browse the full folder contents for customerio-hello-world.

Download Skill

Loading file tree…

plugins/saas-packs/customerio-pack/skills/customerio-hello-world/SKILL.md

Skill Metadata

Name
customerio-hello-world
Description
|

Customer.io Hello World

Overview

Create a minimal working Customer.io integration: identify a user (create/update their profile), track an event, and send a transactional email. This covers the three fundamental Customer.io operations.

Prerequisites

  • customerio-node installed (npm install customerio-node)
  • CUSTOMERIO_SITE_ID and CUSTOMERIO_TRACK_API_KEY configured
  • CUSTOMERIO_APP_API_KEY configured (for transactional email example)

Instructions

Step 1: Identify a User (Create/Update Profile)

// hello-customerio.ts
import { TrackClient, RegionUS } from "customerio-node";

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

// identify() creates the user if they don't exist, or updates if they do.
// The first argument is your internal user ID (immutable — use DB primary key).
await cio.identify("user-123", {
  email: "hello@example.com",          // Required for email campaigns
  first_name: "Jane",
  last_name: "Doe",
  plan: "pro",
  created_at: Math.floor(Date.now() / 1000),  // Unix seconds, NOT milliseconds
});

console.log("User identified in Customer.io");

Key rules:

  • id (first arg) should be your immutable database ID — never use email as ID
  • email attribute is required if you want to send email campaigns
  • created_at must be Unix timestamp in seconds (not ms) — Math.floor(Date.now() / 1000)
  • All custom attributes are stored on the user profile and usable in segments + Liquid templates

Step 2: Track an Event

// Track a custom event on the user's activity timeline.
// Events trigger campaigns — the event name must match exactly in the dashboard.
await cio.track("user-123", {
  name: "signed_up",                   // snake_case, matches campaign trigger
  data: {
    signup_method: "google_oauth",
    referral_source: "product_hunt",
    timestamp: Math.floor(Date.now() / 1000),
  },
});

console.log("Event tracked in Customer.io");

Key rules:

  • User must be identified before tracking events (call identify() first)
  • Event name is case-sensitive and must match your campaign trigger exactly
  • Use snake_case for event names — signed_up, not Signed Up or signedUp
  • data properties are accessible in Liquid templates as {{ event.property_name }}

Step 3: Track an Anonymous Event

// Track events before the user signs up — merge later on identification
await cio.trackAnonymous({
  anonymous_id: "anon-abc-123",         // Your anonymous tracking ID (cookie, device ID)
  name: "page_viewed",
  data: {
    url: "/pricing",
    referrer: "https://google.com",
  },
});

console.log("Anonymous event tracked");

When the anonymous user signs up, include anonymous_id in the identify() call to merge their pre-signup activity:

await cio.identify("user-123", {
  email: "hello@example.com",
  anonymous_id: "anon-abc-123",         // Merges anonymous activity
});

Step 4: Send a Transactional Email

import { APIClient, SendEmailRequest, RegionUS } from "customerio-node";

const api = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, {
  region: RegionUS,
});

const request = new SendEmailRequest({
  to: "hello@example.com",
  transactional_message_id: "1",        // ID from Customer.io dashboard
  message_data: {                       // Populates {{ liquid }} variables
    welcome_name: "Jane",
    login_url: "https://app.example.com/login",
  },
  identifiers: { id: "user-123" },      // Links delivery to user profile
});

const response = await api.sendEmail(request);
console.log("Email queued:", response.delivery_id);

Step 5: Verify in Dashboard

  1. Go to https://fly.customer.io
  2. Navigate to People and search for "hello@example.com"
  3. Verify the profile shows first_name, plan, and other attributes
  4. Click the Activity tab to see the signed_up event
  5. Check Deliveries for the transactional email

Complete Example

// scripts/hello-customerio.ts
import {
  TrackClient, APIClient, SendEmailRequest, RegionUS
} from "customerio-node";

async function main() {
  // Track API client — identify and track
  const cio = new TrackClient(
    process.env.CUSTOMERIO_SITE_ID!,
    process.env.CUSTOMERIO_TRACK_API_KEY!,
    { region: RegionUS }
  );

  // 1. Identify
  await cio.identify("user-hello-world", {
    email: "hello@example.com",
    first_name: "Jane",
    created_at: Math.floor(Date.now() / 1000),
  });
  console.log("1. User identified");

  // 2. Track event
  await cio.track("user-hello-world", {
    name: "hello_world_completed",
    data: { sdk: "customerio-node", step: "quickstart" },
  });
  console.log("2. Event tracked");

  // 3. Clean up test user (optional)
  await cio.suppress("user-hello-world");
  console.log("3. Test user suppressed (won't receive messages)");
}

main().catch(console.error);

Run: npx tsx scripts/hello-customerio.ts

Error Handling

| Error | Cause | Solution | |-------|-------|----------| | 401 Unauthorized | Invalid credentials | Verify Site ID + Track API Key in dashboard | | 400 Bad Request | Malformed payload | Check attribute types and event name format | | User not in People tab | identify() not called | Always call identify() before track() | | Event not in Activity | Dashboard propagation delay | Wait 1-2 minutes and refresh | | Transactional email fails | Wrong transactional_message_id | Verify the ID matches your template in Customer.io |

Resources

Next Steps

After verifying hello world works, proceed to customerio-local-dev-loop to set up your development workflow.