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".
85
83%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
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.
customerio-node configured with Track API credentialsYour 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 emailEvents tracked via the SDK trigger campaigns you build in the dashboard. The SDK sends the data; the dashboard defines the workflow logic.
// 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];// 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 },
});
}
}// 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 });
});In Customer.io dashboard, create campaigns triggered by these events:
Onboarding Campaign:
signed_up{{ customer.first_name }} and {{ event.method }} Liquid)email_verified true?
first_project_created event?
Cancellation Win-back Campaign:
subscription_cancelled{{ event.reason }} Liquid variable| Variable | Source | Example |
|---|---|---|
{{ 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 | Cause | Solution |
|---|---|---|
| Campaign not triggering | Event name mismatch | Event names are case-sensitive — verify exact match |
| User not receiving email | Missing email attribute | Always include email in identify() |
| Duplicate sends | Multiple event fires | Use fire-and-forget with deduplication |
Liquid rendering {{ }} | Missing data property | Ensure data object has all template variables |
After implementing primary workflow, proceed to customerio-core-feature for transactional messages, segments, and broadcasts.
70e9fa4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.