If you’re using Customer.io for running campaigns and PostHog for analytics, you’ve probably wondered how to connect the two. In other words - how do you see in one place, what happens after someone gets an email, push notification, or Slack message?
This guide walks you through exactly how to get that data flowing from Customer.io into PostHog - without overcomplicating things.
Why You’d Want to Do This
Customer.io is where engagement starts. It sends the right message at the right time.
PostHog is where insight happens. It helps you understand what users actually do next.
By connecting them, you get a full picture - for example:
- Did that product launch email get opened - and did the user convert afterwards?
- Which notification type (email, push, Slack) drives the most re-engagement?
- Are your triggered campaigns actually moving the retention needle?
Once you have all your message events inside PostHog, you can answer those questions without exporting CSVs or manually joining data.
Step 1: Pull Data from Customer.io
Customer.io exposes a simple REST API for pulling campaign and message data.
1.1. List all campaigns
GET https://api.customer.io/v1/campaigns
Authorization: Bearer <CIO_APP_API_TOKEN>
Accept: application/json
Example response:
{
"campaigns": [
{
"id": 3,
"name": "Onboarding - Week 2",
"type": "triggered",
"created": 1697011200
}
]
}
Each campaign has its own ID, which you’ll use in the next call.
1.2. Get messages for a campaign
GET https://api.customer.io/v1/campaigns/{campaign_id}/messages
Authorization: Bearer <CIO_APP_API_TOKEN>
Accept: application/json
Example response:
{
"messages": [
{
"id": "dgSW-woAAJq2nQGZtp0BAZaInGWsBG7c9cfSz6UuCw==",
"type": "email",
"customer_id": "2ded2d0e-1a8b-44f2-b1bc-65f89ebdc321",
"metrics": {
"sent": 1746049000,
"delivered": 1746049001,
"human_opened": 1746063121,
"clicked": 1746721399
}
}
]
}
Those UNIX timestamps (metrics.sent, metrics.clicked, etc.) tell you when each event happened.
Depending on the channel, you might also see metrics like:
- pushed, opened, clicked for push notifications
- slack_sent, slack_clicked for Slack campaigns
- triggered, completed for webhooks
Step 2: Map Customer.io Metrics to PostHog Events
Every metric you see in Customer.io can be represented as a PostHog event.Here’s a simple mapping you can start with:
| Channel |
Customer.io Metric |
PostHog Event Name |
| Email |
delivered |
cio:email_delivered |
| Email |
human_opened |
cio:email_opened |
| Email |
clicked |
cio:email_clicked |
| Push |
pushed |
cio:push_sent |
| Push |
opened |
cio:push_opened |
| Push |
clicked |
cio:push_clicked |
| Slack |
slack_sent |
cio:slack_sent |
| Slack |
slack_clicked |
cio:slack_clicked |
How would you attribute events to the right person in PostHog? Use the Customer.io person ID as the PostHog distinct_id - that’s your unique user reference.
This is based on the assumption that you have a “Destination” data pipeline set up for Customer.io in PostHog to give Customer.io persons a PostHog identifier.
With the records pulled from Customer.io, you can transform them in PostHog event format and send it to PostHog using its API.
PostHog’s /batch API is designed for high-volume ingestion. You can send up to 500 events at a time, and gzip-compress the payload for efficiency. Here’s a python snippet:
import gzip, json, requests
payload = {
"api_key": "<PH_API_KEY>",
"batch": [
{
"event": "cio:email_delivered",
"distinct_id": "2ded2d0e-1a8b-44f2-b1bc-65f89ebdc321",
"timestamp": "2025-10-14T17:13:34Z",
"properties": {
"source": "customer_io",
"channel": "email",
"campaign_id": 3,
"campaign_name": "Onboarding - Week 2",
"message_id": "dgSW-woAAJq2nQGZtp0BAZaInGWsBG7c9cfSz6UuCw=="
}
}
]
}
gz = gzip.compress(json.dumps(payload).encode("utf-8"))
requests.post(
"https://app.posthog.com/batch/",
headers={"Content-Type": "application/json", "Content-Encoding": "gzip"},
data=gz
)
Once it’s in PostHog, you’ll see these events side-by-side with your product analytics.
Step 4: Make It Incremental (and Safe to Re-run)
If you’re running this regularly (say, daily), you’ll want to avoid sending duplicates.
The easiest way is to keep a small tracking table in your data warehouse - like Redshift, BigQuery, SQLite, etc.
CREATE TABLE IF NOT EXISTS public.cio_event_log (
message_id VARCHAR(255),
metric_type VARCHAR(50),
campaign_id INT,
created_at TIMESTAMP DEFAULT GETDATE(),
PRIMARY KEY (message_id, metric_type)
);
Before sending, check if the (message_id, metric_type) already exists.
If it does, skip it.
If not, send it to PostHog and log it in the table.
This makes your job run over and over again without ever resending old data.
Step 5: Ensure Privacy, Security, and Best Practices
A few critical points to keep in mind while setting up your pipeline:
- Never send emails or phone numbers. Use customer_id or a hashed identifier.
- Throttle your API calls. Customer.io’s API has rate limits (~10 requests/sec).
- Use gzip and batching for efficient delivery to PostHog.
- Log every batch you send - it makes debugging painless.
Step 6: Automate it
Great! You have the ETL logic made. Now we need to set up a running schedule to get new data in PostHog as it comes - preferably run every 24 hours.
If you have AWS set up, we suggest running this pipeline as a Glue job. Glue can:
- Fetch Customer.io data daily
- Compare it with your Redshift table
- Push only new events to PostHog
- Log completion metrics and notify Slack on failures
It’s reliable, serverless, and can handle hundreds of thousands of events per run.
Otherwise, you can set this up easily on any server that supports Python execution, making requests and Cron jobs.
Step 7: How to Use this Data in PostHog?
Once you have these events in, you can make all kinds of insights to get the full picture. The most useful insight you can make is a “Funnel”.
With a Funnel, you can add steps - for example:
Email Delivered → Email Opened → Feature Landing page → Feature Adoption
You’ll be able to track how many persons entered the funnel, conversion and drop-off numbers at every step, conversion time, and a whole lot more!
The Takeaway
Customer.io is how you talk to users.
PostHog is how you understand them.
By syncing these two, you connect your messaging layer to your product insights - so you’re not guessing what impact campaigns have. You’re measuring it.