How to Track Customers Across Multiple Micro-SaaS Products
You've built more than one product. You have users across all of them, but no way to tell whether John from Product A is the same John from Product B. Your most loyal customers — the ones buying everything you ship — are invisible to you because your data lives in separate silos.
This guide explains how to architect customer tracking for a multi-product setup, with concrete implementation steps and the data model that makes unified views possible.
Why multi-product tracking is different
When you have a single product, customer tracking is straightforward: one database, one user table, one source of truth. The complexity arrives with the second product.
Each product has its own database, its own auth system, its own users table. A customer who uses both products appears twice — as two separate records in two separate systems — unless you explicitly build a merge mechanism.
Most developers handle this in one of three ways:
- Spreadsheets per product — fastest to set up, breaks down quickly with volume
- Manual cross-referencing — opening multiple tabs and matching emails manually
- Shared external CRM — but traditional CRMs weren't designed for programmatic multi-product data
The right solution is a customer database that treats email as a global merge key, and treats each product as a separate project that can be associated with a customer.
The email-as-merge-key principle
Email is the only reliable cross-product identifier. People use the same email for multiple products (most of the time). It's human-readable, stable over time, and unique per person.
The data model that makes this work:
customers (global, per account)
- id
- email ← merge key, unique per account
- name
- created_at
customer_projects (join table)
- customer_id
- project_id ← which product
- status ← free/paid per product
- params ← custom data per product (JSONB)
- last_activity_atWith this model, a customer using three of your products appears as one row in the customers table, with three rows in customer_projects. You get a unified profile with per-product details.
Setting up multi-product tracking with TinyCRM
In TinyCRM, each product is a project. Each project has its own API key. You install the SDK in each product's codebase with a different API key, but all data goes to the same account and merges by email.
Product A setup:
// Product A — .env
TINYCRM_API_KEY=tcrm_proj_aaa...
// Product A — lib/tinycrm.ts
import { TinyCRM } from 'tinycrm-sdk';
export const tinycrm = new TinyCRM(process.env.TINYCRM_API_KEY!);
// Product A — on signup
await tinycrm.identify({
email: user.email,
name: user.name,
status: user.isPaid ? 'paid' : 'free',
params: { source: 'product-hunt', product: 'product-a' },
});Product B setup (identical pattern, different API key):
// Product B — .env
TINYCRM_API_KEY=tcrm_proj_bbb...
// Product B — on signup
await tinycrm.identify({
email: user.email,
name: user.name,
status: 'free',
params: { source: 'twitter', product: 'product-b' },
});When the same user signs up for both products, TinyCRM creates one customer record and two customer_project records. In your dashboard, they appear as one row with two project badges.
Filtering your unified customer view
Once data is flowing from all your products, the dashboard becomes genuinely useful. You can filter to answer questions like:
- "Show me everyone who uses Product A" — filter by project
- "Show me paid customers in Product B" — filter by project + status
- "Who uses both Product A and Product B?" — filter by multiple project badges
- "Who hasn't been active in 30 days?" — sort by last_activity_at
- "Which customers came from Product Hunt?" — filter by params where source = product-hunt
Handling edge cases in multi-product tracking
Different emails per product: Some customers use different email addresses for different products. This creates separate customer records. There's no automatic way to merge these — you'd need to manually merge via the dashboard or API if you know they're the same person.
Name conflicts: If a customer uses their full name in Product A and just a first name in Product B, the most recent identify() call updates the name globally. You can avoid this by only passing name when you're confident it's accurate.
Params isolation: Params are per-project, not global. A customer's plan in Product A doesn't affect their plan in Product B. This is the right behavior — products have independent subscription states.
The strategic value of unified customer data
Once you have all your products in one customer database, you start seeing patterns that are invisible when data is siloed:
- Cross-sell opportunities: Who are your Product A customers who haven't tried Product B yet? That's a warm audience.
- Power users: Customers using 3+ of your products are your advocates. Know who they are.
- Churn correlation: Does churn in Product A correlate with churn in Product B? Are you losing the same people across products?
- Acquisition channels: Which product attracts customers who then try other products? That's your acquisition funnel.
Migrating existing data into a unified view
If you already have customer data in multiple places, you don't have to start from scratch. For each product:
- Export your existing users as CSV
- Use TinyCRM's CSV import to load them into the appropriate project
- Set up the SDK for ongoing automatic sync
See our spreadsheet migration guide for step-by-step import instructions.
See all your customers in one place
TinyCRM merges customer data across all your products into a single table. One SDK integration per product, one unified view.