> customerio-primary-workflow

Implement Customer.io primary messaging workflow. Use when setting up campaign triggers, welcome sequences, onboarding flows, or event-driven email automation. Trigger: "customer.io campaign", "customer.io workflow", "customer.io email automation", "customer.io messaging", "customer.io onboarding".

fetch
$curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/customerio-primary-workflow?format=md"
SKILL.mdcustomerio-primary-workflow

Customer.io Primary Workflow

Overview

Implement Customer.io's core messaging workflow: identify users with segment-ready attributes, track lifecycle events that trigger campaigns, and set up the data layer for automated onboarding, nurture, and re-engagement sequences.

Prerequisites

  • customerio-node configured with Track API credentials
  • Campaigns created in Customer.io dashboard (triggered by events you define)
  • Understanding of your user lifecycle stages

How Campaigns Work

Your App (SDK)           Customer.io Dashboard          User
─────────────           ────────────────────          ────
cio.identify(user)  →   Profile created/updated
cio.track("signed_up")  →   Campaign trigger fires
                             Wait 1 day             →   Welcome email
                             Check: verified?
                             ├─ No                  →   Verification reminder
                             └─ Yes → Wait 3 days   →   Feature tips email

Events tracked via the SDK trigger campaigns you build in the dashboard. The SDK sends the data; the dashboard defines the workflow logic.

Instructions

Step 1: Define Your Event Taxonomy

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

// Central event definitions — every event your app tracks
export const CIO_EVENTS = {
  // Onboarding
  SIGNED_UP: "signed_up",
  EMAIL_VERIFIED: "email_verified",
  PROFILE_COMPLETED: "profile_completed",
  FIRST_PROJECT_CREATED: "first_project_created",

  // Engagement
  FEATURE_USED: "feature_used",
  INVITED_TEAMMATE: "invited_teammate",
  UPGRADE_STARTED: "upgrade_started",
  UPGRADE_COMPLETED: "upgrade_completed",

  // Lifecycle
  SUBSCRIPTION_RENEWED: "subscription_renewed",
  SUBSCRIPTION_CANCELLED: "subscription_cancelled",
  TRIAL_EXPIRING: "trial_expiring",

  // Commerce
  CHECKOUT_STARTED: "checkout_started",
  CHECKOUT_COMPLETED: "checkout_completed",
  REFUND_REQUESTED: "refund_requested",
} as const;

type EventName = (typeof CIO_EVENTS)[keyof typeof CIO_EVENTS];

Step 2: Build the Messaging Service

// services/customerio-messaging.ts
import { TrackClient, RegionUS } from "customerio-node";
import { CIO_EVENTS } from "../lib/customerio-events";

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

interface UserProfile {
  id: string;
  email: string;
  firstName: string;
  lastName?: string;
  plan: string;
  companyName?: string;
}

export class MessagingService {
  /** Call on user signup — creates profile and triggers onboarding campaign */
  async onSignup(user: UserProfile, signupMethod: string): Promise<void> {
    // 1. Identify with all attributes campaigns need
    await cio.identify(user.id, {
      email: user.email,
      first_name: user.firstName,
      last_name: user.lastName ?? "",
      plan: user.plan,
      company: user.companyName ?? "",
      created_at: Math.floor(Date.now() / 1000),
      onboarding_step: "signed_up",
    });

    // 2. Track the event that triggers the onboarding campaign
    await cio.track(user.id, {
      name: CIO_EVENTS.SIGNED_UP,
      data: {
        method: signupMethod,  // "google", "email", "github"
        plan: user.plan,
      },
    });
  }

  /** Call when user verifies email — updates profile + tracks event */
  async onEmailVerified(userId: string): Promise<void> {
    await cio.identify(userId, {
      email_verified: true,
      email_verified_at: Math.floor(Date.now() / 1000),
      onboarding_step: "verified",
    });

    await cio.track(userId, {
      name: CIO_EVENTS.EMAIL_VERIFIED,
    });
  }

  /** Call on feature usage — drives engagement segments and campaigns */
  async onFeatureUsed(
    userId: string,
    feature: string,
    metadata?: Record<string, any>
  ): Promise<void> {
    await cio.track(userId, {
      name: CIO_EVENTS.FEATURE_USED,
      data: { feature, ...metadata },
    });

    // Update engagement metrics on the profile for segmentation
    await cio.identify(userId, {
      last_active_at: Math.floor(Date.now() / 1000),
    });
  }

  /** Call on plan upgrade — triggers upgrade confirmation campaign */
  async onUpgrade(userId: string, from: string, to: string, mrr: number): Promise<void> {
    await cio.identify(userId, {
      plan: to,
      mrr,
      upgraded_at: Math.floor(Date.now() / 1000),
    });

    await cio.track(userId, {
      name: CIO_EVENTS.UPGRADE_COMPLETED,
      data: { from_plan: from, to_plan: to, mrr },
    });
  }

  /** Call on cancellation — triggers win-back campaign */
  async onCancellation(userId: string, reason: string): Promise<void> {
    await cio.identify(userId, {
      plan: "cancelled",
      cancelled_at: Math.floor(Date.now() / 1000),
      cancellation_reason: reason,
    });

    await cio.track(userId, {
      name: CIO_EVENTS.SUBSCRIPTION_CANCELLED,
      data: { reason },
    });
  }
}

Step 3: Integrate into Application Routes

// routes/auth.ts (Express example)
import { MessagingService } from "../services/customerio-messaging";

const messaging = new MessagingService();

router.post("/signup", async (req, res) => {
  const user = await db.createUser(req.body);

  // Fire-and-forget — don't block the signup response
  messaging.onSignup(
    {
      id: user.id,
      email: user.email,
      firstName: user.firstName,
      plan: user.plan,
    },
    req.body.signupMethod
  ).catch((err) => console.error("CIO signup tracking failed:", err));

  res.json({ user });
});

router.post("/verify-email", async (req, res) => {
  await db.verifyEmail(req.user.id);
  messaging.onEmailVerified(req.user.id).catch(console.error);
  res.json({ verified: true });
});

Step 4: Dashboard Campaign Configuration

In Customer.io dashboard, create campaigns triggered by these events:

Onboarding Campaign:

  1. Trigger: Event signed_up
  2. Wait 5 minutes
  3. Send welcome email (use {{ customer.first_name }} and {{ event.method }} Liquid)
  4. Wait 1 day
  5. Branch: Is email_verified true?
    • No → Send verification reminder
    • Yes → Continue
  6. Wait 3 days
  7. Send feature tips email
  8. Wait 7 days
  9. Branch: Has first_project_created event?
    • No → Send activation nudge
    • Yes → End (move to engagement campaign)

Cancellation Win-back Campaign:

  1. Trigger: Event subscription_cancelled
  2. Wait 3 days
  3. Send "We miss you" email with {{ event.reason }} Liquid variable
  4. Wait 7 days
  5. Send discount offer email

Liquid Template Variables

VariableSourceExample
{{ customer.first_name }}identify() attributes"Jane"
{{ customer.plan }}identify() attributes"pro"
{{ event.method }}track() event data"google"
{{ event.reason }}track() event data"too_expensive"

Error Handling

ErrorCauseSolution
Campaign not triggeringEvent name mismatchEvent names are case-sensitive — verify exact match
User not receiving emailMissing email attributeAlways include email in identify()
Duplicate sendsMultiple event firesUse fire-and-forget with deduplication
Liquid rendering {{ }}Missing data propertyEnsure data object has all template variables

Resources

Next Steps

After implementing primary workflow, proceed to customerio-core-feature for transactional messages, segments, and broadcasts.

┌ stats

installs/wk0
░░░░░░░░░░
github stars1.7K
██████████
first seenMar 23, 2026
└────────────

┌ repo

jeremylongshore/claude-code-plugins-plus-skills
by jeremylongshore
└────────────