| name | customerio-reference-architecture |
| description | Implement Customer.io reference architecture.
Use when designing integrations, planning architecture,
or implementing enterprise patterns.
Trigger with phrases like "customer.io architecture", "customer.io design",
"customer.io enterprise", "customer.io integration pattern".
|
| allowed-tools | Read, Write, Edit, Bash(gh:*), Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Customer.io Reference Architecture
Overview
Enterprise-grade reference architecture for Customer.io integration with proper separation of concerns, reliability, and scalability.
Architecture Diagram
Customer.io
|
+-------------------+-------------------+
| | |
Track API App API Webhooks
| | |
v v v
+-------+-------+ +-------+-------+ +-------+-------+
| Event Bus | | Transactional | | Webhook Handler|
| (Kafka) | | Service | | (Express) |
+-------+-------+ +-------+-------+ +-------+-------+
| | |
v v v
+-------+-------+ +-------+-------+ +-------+-------+
| CustomerIO | | Email | | Event |
| Worker | | Templates | | Processor |
+-------+-------+ +-------+-------+ +-------+-------+
| | |
+-------------------+-------------------+
|
v
+-------+-------+
| Data Lake |
| (BigQuery) |
+---------------+
Instructions
Step 1: Core Service Layer
import { TrackClient, APIClient, RegionUS } from '@customerio/track';
import { EventEmitter } from 'events';
export interface CustomerIOConfig {
trackSiteId: string;
trackApiKey: string;
appApiKey: string;
region: 'us' | 'eu';
environment: 'development' | 'staging' | 'production';
}
export class CustomerIOService extends EventEmitter {
private trackClient: TrackClient;
private apiClient: APIClient;
private config: CustomerIOConfig;
constructor(config: CustomerIOConfig) {
super();
this.config = config;
this.trackClient = new TrackClient(
config.,
config.,
{ : config. === ? : }
);
. = (config., {
: config. === ? :
});
}
(: , : ): <> {
.(, { userId, attributes });
{
..(userId, {
...attributes,
: ..,
: .(.() / )
});
.(, { userId });
} (error) {
.(, { userId, error });
error;
}
}
(: , : ): <> {
.(, { userId, event });
{
..(userId, {
: event.,
: {
...event.,
: ..
}
});
.(, { userId, : event. });
} (error) {
.(, { userId, : event., error });
error;
}
}
(: ): <> {
..(request);
}
}
Step 2: Event Bus Integration
import { Kafka, Producer, Consumer } from 'kafkajs';
import { CustomerIOService } from './index';
interface CustomerIOEvent {
type: 'identify' | 'track' | 'transactional';
userId: string;
payload: any;
timestamp: number;
correlationId: string;
}
export class CustomerIOEventBus {
private producer: Producer;
private consumer: Consumer;
private service: CustomerIOService;
constructor(kafka: Kafka, service: CustomerIOService) {
this.producer = kafka.producer();
this.consumer = kafka.consumer({ groupId: 'customerio-worker' });
this. = service;
}
(): <> {
..();
..();
..({
: [, , ]
});
..({
: ({ topic, message }) => {
: = .(message.!.());
.(topic, event);
}
});
}
(: , : ): <> {
startTime = .();
{
(event.) {
:
..(event., event.);
;
:
..(event., event.);
;
:
..(event.);
;
}
..({
: ,
: [{
: event.,
: .({
...event,
: ,
: .() - startTime
})
}]
});
} (error) {
..({
: ,
: [{
: event.,
: .({
...event,
: ,
: error.,
: .() - startTime
})
}]
});
}
}
(: ): <> {
..({
: ,
: [{
: event.,
: .(event)
}]
});
}
}
Step 3: Repository Pattern
import { CustomerIOService } from '../services/customerio';
import { UserRepository } from './user';
export interface MessagingPreferences {
email: boolean;
push: boolean;
sms: boolean;
inApp: boolean;
}
export class UserMessagingRepository {
constructor(
private cio: CustomerIOService,
private users: UserRepository
) {}
async syncUser(userId: string): Promise<void> {
const user = await this.users.findById(userId);
if (!user) throw new Error(`User ${userId} not found`);
const preferences = await this.getPreferences(userId);
..(userId, {
: user.,
: user.,
: user.,
: .(user..() / ),
: user.?. || ,
: preferences.,
: preferences.,
: preferences.
});
}
(: ): <> {
{
: ,
: ,
: ,
:
};
}
(
: ,
: <>
): <> {
.(userId, preferences);
..(userId, {
: preferences.,
: preferences.,
: preferences.
});
}
}
Step 4: Webhook Handler
import { Router } from 'express';
import { EventEmitter } from 'events';
export class CustomerIOWebhooks extends EventEmitter {
private router: Router;
private signingSecret: string;
constructor(signingSecret: string) {
super();
this.signingSecret = signingSecret;
this.router = Router();
this.setupRoutes();
}
private setupRoutes(): void {
this.router.post('/', async (req, res) => {
if (!this.verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
events = req.. || [];
( event events) {
.(event., event);
.(, event);
}
res.().({ : events. });
});
}
(): {
.;
}
}
webhooks = (process..!);
webhooks.(, {
});
webhooks.(, (event) => {
cio.(event..);
});
webhooks.(, {
(event);
});
app.(, webhooks.());
Step 5: Infrastructure as Code
# terraform/customerio.tf
resource "google_secret_manager_secret" "customerio_site_id" {
secret_id = "customerio-site-id"
replication {
auto {}
}
}
resource "google_secret_manager_secret" "customerio_api_key" {
secret_id = "customerio-api-key"
replication {
auto {}
}
}
resource "google_cloud_run_service" "customerio_worker" {
name = "customerio-worker"
location = var.region
template {
spec {
containers {
image = "gcr.io/${var.project}/customerio-worker:latest"
env {
name = "CUSTOMERIO_SITE_ID"
value_from {
secret_key_ref {
name = google_secret_manager_secret.customerio_site_id.secret_id
key = "latest"
}
}
}
env {
name = "CUSTOMERIO_API_KEY"
value_from {
secret_key_ref {
name = google_secret_manager_secret.customerio_api_key.secret_id
key = "latest"
}
}
}
}
}
}
}
resource "google_pubsub_topic" "customerio_events" {
name = "customerio-events"
}
resource "google_bigquery_dataset" "customerio" {
dataset_id = "customerio_events"
location = var.region
}
resource "google_bigquery_table" "delivery_events" {
dataset_id = google_bigquery_dataset.customerio.dataset_id
table_id = "delivery_events"
schema = file("${path.module}/schemas/delivery_events.json")
time_partitioning {
type = "DAY"
field = "timestamp"
}
}
Architecture Principles
- Separation of Concerns: Track API, App API, and Webhooks are handled by separate services
- Event-Driven: Use message queues for reliable async processing
- Idempotency: All operations can be safely retried
- Observability: Events are emitted for monitoring and debugging
- Infrastructure as Code: All resources defined in Terraform
Output
- Core Customer.io service layer
- Event bus integration (Kafka)
- Repository pattern for user messaging
- Webhook handler with signature verification
- Terraform infrastructure code
Resources
Next Steps
After implementing architecture, proceed to customerio-multi-env-setup for multi-environment configuration.