Customerio
Send transactional and marketing email with Customer.io. Use this skill when the project needs to integrate Customer.io for behavioral email automation, transactional messages via the Transactional API, event-driven campaigns, segment-based messaging, webhook handling, and customer data management. Covers the Track API, Transactional API, campaign workflows, and data-driven personalization.
You are an expert in Customer.io for transactional and marketing email. You understand
the Track API for customer data and events, the Transactional API for triggered sends,
campaign workflows, segmentation, Liquid templating, and behavioral email automation.
## Key Points
- **Confusing the two API keys** -- The Track API uses site_id + API key. The Transactional API uses a separate App API key. Mixing them causes auth errors with unhelpful error messages.
- **Use the Track API for campaigns, Transactional API for triggered sends.** Campaign
- **Send `created_at` as a Unix timestamp.** Customer.io uses this to anchor time-based
- **Use Liquid templates for personalization.** Customer.io templates support Liquid.
- **Segment in Customer.io, not in your code.** Let Customer.io handle audience
- **Set up a suppression workflow.** When you receive bounce or spam webhooks, update
- **Prefer identified users over anonymous events.** Anonymous events are useful for
- **Confusing the two API keys.** The Track API uses site_id + API key. The
- **Sending strings instead of Unix timestamps.** Customer.io expects integer Unix
- **Not idempotently identifying users.** Calling `identify` with a new email address
- **Exceeding the Track API rate limit.** The Track API allows ~100 requests/second
- **Forgetting to handle unsubscribe in transactional messages.** Transactional
## Quick Example
```bash
npm install customerio-node
```
```typescript
import { APIClient, SendEmailRequest, RegionUS } from "customerio-node";
const api = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, {
region: RegionUS,
});
```skilldb get email-services-skills/customerioFull skill: 275 linesCustomer.io — Email Service
You are an expert in Customer.io for transactional and marketing email. You understand the Track API for customer data and events, the Transactional API for triggered sends, campaign workflows, segmentation, Liquid templating, and behavioral email automation.
Core Philosophy
Customer.io bridges the gap between developer-managed data and marketer-designed campaigns. The developer's responsibility is to keep the customer data model accurate and current -- identifying users with correct attributes and tracking events that represent meaningful business actions. The marketer's responsibility is to build campaigns in the visual workflow builder that react to those events and segments. When this separation is clean, marketing can iterate on email content and targeting without requiring code changes.
Events are the heartbeat of Customer.io. Every significant user action -- completing onboarding, making a purchase, upgrading a plan, abandoning a cart -- should be tracked as a named event with structured data. These events trigger campaign workflows, update segments, and drive personalization. A Customer.io workspace with rich event data enables sophisticated behavioral automation; one with only identify calls is just an expensive contact database.
Keep transactional and campaign email on separate rails. The Transactional API exists for immediate, one-off messages (password resets, receipts, security alerts) that must bypass campaign unsubscribe preferences. Campaign emails (onboarding drips, re-engagement sequences, product announcements) go through the workflow builder and respect user preferences. Mixing these -- sending marketing content through the Transactional API -- violates consent laws and erodes trust.
Anti-Patterns
- Sending marketing content through the Transactional API -- The Transactional API bypasses unsubscribe preferences by design. Using it for promotional content violates CAN-SPAM/GDPR and damages sender reputation.
- Passing ISO date strings instead of Unix timestamps -- Customer.io expects integer Unix timestamps for date attributes. ISO strings cause silent failures in time-based segments and campaign delays.
- Looping individual identify calls for bulk imports -- The Track API rate limit is approximately 100 requests/second. Bulk importing thousands of users via individual calls risks rate limiting. Use the batch endpoint or CSV import.
- Duplicating audience logic in application code -- Let Customer.io's segment builder handle targeting. Your code should focus on keeping attributes and events accurate; segmentation belongs in the platform.
- Confusing the two API keys -- The Track API uses site_id + API key. The Transactional API uses a separate App API key. Mixing them causes auth errors with unhelpful error messages.
Overview
Customer.io is a messaging automation platform that sends emails (and push, SMS, in-app) triggered by customer behavior. Unlike pure transactional email APIs, Customer.io combines a customer data platform with a visual workflow builder. Developers feed customer attributes and events via the Track API; marketers build campaigns in the UI that react to those events. The separate Transactional API handles one-off triggered messages (password resets, receipts) outside of campaign workflows.
Setup & Configuration
Installation
npm install customerio-node
Track API client (for customer data and events)
import { TrackClient, RegionUS, RegionEU } from "customerio-node";
const tracker = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_API_KEY!,
{ region: RegionUS } // or RegionEU for EU data center
);
Transactional API client (for triggered sends)
import { APIClient, SendEmailRequest, RegionUS } from "customerio-node";
const api = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, {
region: RegionUS,
});
Environment variables
CUSTOMERIO_SITE_ID=your-site-id
CUSTOMERIO_API_KEY=your-tracking-api-key
CUSTOMERIO_APP_API_KEY=your-app-api-key
Domain authentication
Configure your sending domain in Customer.io Settings > Email. Add the provided DKIM and SPF DNS records. Enable a custom tracking domain for link tracking to align with your brand and improve deliverability under strict DMARC policies.
Core Patterns
Identify a customer (create or update)
async function identifyCustomer(
userId: string,
attributes: Record<string, unknown>
) {
await tracker.identify(userId, {
email: attributes.email,
first_name: attributes.firstName,
created_at: Math.floor(Date.now() / 1000), // Unix timestamp
plan: attributes.plan,
...attributes,
});
}
// Usage — call on signup or profile update
await identifyCustomer("user_123", {
email: "alice@example.com",
firstName: "Alice",
plan: "pro",
});
Track an event (triggers campaign workflows)
async function trackEvent(
userId: string,
eventName: string,
data: Record<string, unknown> = {}
) {
await tracker.track(userId, {
name: eventName,
data,
});
}
// Usage — this event can trigger a campaign in the workflow builder
await trackEvent("user_123", "order_completed", {
orderId: "ORD-456",
total: 79.99,
items: ["Widget A", "Widget B"],
});
Send a transactional email
async function sendTransactional(
to: string,
transactionalMessageId: number,
data: Record<string, unknown>
) {
const request = new SendEmailRequest({
to,
transactional_message_id: transactionalMessageId,
message_data: data,
identifiers: { email: to },
});
const response = await api.sendEmail(request);
return response; // { delivery_id, queued_at }
}
// Usage — transactional message created in Customer.io UI
await sendTransactional("alice@example.com", 1, {
resetUrl: "https://app.example.com/reset?token=abc",
expiresIn: "24 hours",
});
Track anonymous events (pre-signup)
async function trackAnonymousEvent(
anonymousId: string,
eventName: string,
data: Record<string, unknown>
) {
await tracker.trackAnonymous(anonymousId, {
name: eventName,
data,
});
}
// Later, when user signs up, merge anonymous activity:
await tracker.mergeCustomers("cio_" + anonymousId, "user_123");
Delete a customer (GDPR compliance)
async function deleteCustomer(userId: string) {
await tracker.destroy(userId);
}
Handle webhooks for reporting
import { Router, Request, Response } from "express";
import crypto from "crypto";
const router = Router();
function verifySignature(req: Request): boolean {
const signature = req.headers["x-cio-signature"] as string;
const hmac = crypto
.createHmac("sha256", process.env.CUSTOMERIO_WEBHOOK_SECRET!)
.update(JSON.stringify(req.body))
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac));
}
router.post("/webhooks/customerio", (req: Request, res: Response) => {
if (!verifySignature(req)) {
return res.status(401).end();
}
const event = req.body;
switch (event.metric) {
case "delivered":
// Email reached inbox
break;
case "opened":
// Recipient opened
break;
case "clicked":
// Link clicked — event.data.href has the URL
break;
case "bounced":
// Handle bounce
break;
case "spammed":
// Spam complaint — suppress address
break;
case "unsubscribed":
// User opted out
break;
}
res.status(200).end();
});
Best Practices
- Use the Track API for campaigns, Transactional API for triggered sends. Campaign emails (onboarding drips, re-engagement) are driven by events and segments in the workflow builder. One-off messages (password resets, receipts) go through the Transactional API for immediate, reliable delivery.
- Send
created_atas a Unix timestamp. Customer.io uses this to anchor time-based segments and campaign delays. Without it, time-based workflows will not trigger correctly. - Use Liquid templates for personalization. Customer.io templates support Liquid.
Pass structured data in events and attributes so templates can use conditionals
and loops:
{% if customer.plan == "pro" %}. - Segment in Customer.io, not in your code. Let Customer.io handle audience targeting via its segment builder. Your code should focus on keeping attributes and events accurate and up to date.
- Set up a suppression workflow. When you receive bounce or spam webhooks, update your internal records. Customer.io handles suppression automatically for campaigns, but your app should reflect these states in its own user model.
- Prefer identified users over anonymous events. Anonymous events are useful for pre-signup tracking, but merge them into identified profiles as soon as possible to avoid orphaned data.
Common Pitfalls
- Confusing the two API keys. The Track API uses site_id + API key. The Transactional API uses a separate App API key. Mixing them up causes auth errors with no helpful message.
- Sending strings instead of Unix timestamps. Customer.io expects integer Unix timestamps for date attributes. Passing ISO strings causes silent filtering failures in segments.
- Not idempotently identifying users. Calling
identifywith a new email address for an existing user_id updates the profile — it does not create a duplicate. But calling it with a new user_id and the same email creates a duplicate. Deduplicate by user_id, not email. - Exceeding the Track API rate limit. The Track API allows ~100 requests/second per workspace by default. For bulk imports, use the batch endpoint or the Customer.io data import CSV feature instead of looping identify calls.
- Forgetting to handle unsubscribe in transactional messages. Transactional messages bypass campaign unsubscribe preferences by design. If you send marketing content through the Transactional API, you violate consent — keep transactional content strictly transactional.
Install this skill directly: skilldb add email-services-skills
Related Skills
Email Deliverability
Optimize email deliverability across any provider. Use this skill when the project needs to improve inbox placement, configure SPF, DKIM, and DMARC, manage sender reputation, handle bounces and complaints, warm new IPs or domains, or debug delivery issues. Provider-agnostic best practices for getting email into the inbox.
Loops
Send transactional and marketing email with Loops. Use this skill when the project needs to integrate Loops for event-driven email, automated sequences, transactional sends, contact management, or product-led email marketing. Covers the Loops API, events, transactional sends, contacts, and audience management.
Mailchimp Transactional
Send transactional email with Mailchimp Transactional (formerly Mandrill). Use this skill when the project needs to integrate Mandrill/Mailchimp Transactional for reliable transactional email, template-based sending, metadata tracking, subaccounts, webhook processing, or inbound email routing. Covers the Mandrill API, templates, subaccounts, metadata, and webhooks.
Mailgun
Send transactional and marketing email with Mailgun. Use this skill when the project needs to integrate Mailgun for email sending via API or SMTP, mailing lists, email validation, template management, route-based inbound processing, event tracking, or deliverability optimization. Covers the Mailgun REST API, templates, mailing lists, routes, webhooks, and email validation.
Mailjet
Send transactional and marketing email with Mailjet. Use this skill when the project needs to integrate Mailjet for email delivery via REST API or SMTP, template management with MJML, contact list management, segmentation, real-time event tracking, send-time optimization, and A/B testing. Covers the Send API v3.1, template language, contact management, and event webhooks.
Mailtrap
Send and test email with Mailtrap. Use this skill when the project needs to integrate Mailtrap for email testing in development, transactional email sending in production, email previewing, HTML validation, spam score checking, or sandbox email capture. Covers both Mailtrap Email Testing (sandbox) and Mailtrap Email Sending (production transactional API).