Use Case

See who converts from free to paid

Every SaaS product has a conversion problem hiding in plain sight. Somewhere in your user table there are trial users who have been on a free plan for six weeks. There are churned customers who cancelled three months ago and you never noticed. There are paid customers on your lowest plan who are using the product every day and are obviously ready to upgrade. TinyCRM makes all of this visible — filtered, searchable, and always current.

The problem

Knowing that 12% of your trials convert sounds useful, but it doesn't tell you which 12%. The founders who grow their SaaS products are the ones who can name their churned users, look them up, and understand what happened. Aggregate conversion metrics live in your analytics dashboard. The individual stories that explain those metrics live nowhere — or in a spreadsheet that was last updated two months ago.

The mechanics of the problem are straightforward. Most SaaS products have a users table with an email column and maybe a plan column. There is no standard way to query "show me all users who signed up more than 14 days ago and are still on the free plan." That requires a SQL query, a Metabase dashboard, or exporting the database and filtering in Excel. None of those options are fast, and none of them update themselves.

Running multiple products makes this worse. Each product has its own user table with its own schema. Seeing trial conversion across a portfolio of three products means querying three databases separately and trying to reconcile the results — if the email addresses even match across systems.

How TinyCRM solves it

TinyCRM uses a simple four-value status field — free, trial, paid, churned — on every customer-project relationship. You set it when you call identify() and update it whenever it changes. The dashboard lets you filter by any combination of status and project in seconds.

Set status on identify()

Pass status: 'trial' when a user starts a trial, status: 'free' for a freemium signup. The status is stored per customer-project and immediately filterable in the dashboard.

Update status from webhooks

Call identify() again from your Stripe or Paddle webhook handler when a payment succeeds. Pass status: 'paid' and the customer's row updates instantly — no manual work, no cron job.

Filter by status in the dashboard

The customer table has a status filter. Click 'trial' to see every trial user across all projects. Add a project filter to narrow it to a specific product. Find the exact segment you care about in seconds.

Track churn with status: 'churned'

When a subscription cancels, call identify() with status: 'churned'. Churned customers stay in the table — you can still look them up, see their history, and understand what they were using.

Track the customer lifecycle

Every customer moves through stages. TinyCRM lets you model that lifecycle explicitly with the status field and keep it accurate by calling identify() at each transition point.

Lifecycle flow

Sign-up handler     → identify({ status: "free" })
Trial activation    → identify({ status: "trial" })
Payment webhook     → identify({ status: "paid", params: { plan, mrr } })
Cancellation hook   → identify({ status: "churned" })

Each call is an upsert — if the customer already exists for that project, their record updates. If they're new, a record is created. The dashboard always reflects the latest state without any background jobs or reconciliation logic on your end.

From trial to paid — in code

Two identify() calls at two different points in your backend — one on sign-up, one when payment succeeds. That is all it takes to track a full trial-to-paid conversion in TinyCRM.

Step 1 — Sign-up handler: mark as trial

import { TinyCRM } from "tinycrm-sdk";

const crm = new TinyCRM({
  apiKey: process.env.TINYCRM_API_KEY,
});

// app/api/auth/signup/route.ts
export async function POST(req: Request) {
  const { email, name } = await req.json();
  const user = await db.user.create({ data: { email, name } });

  // Track as trial — fire-and-forget
  crm.identify({
    email: user.email,
    name: user.name,
    status: "trial",
    params: {
      trial_started_at: new Date().toISOString(),
      source: "organic",
    },
  });

  return Response.json({ ok: true });
}

Step 2 — Payment webhook: update to paid

// app/api/webhooks/paddle/route.ts
export async function POST(req: Request) {
  const event = await verifyPaddleWebhook(req);

  if (event.event_type === "subscription.activated") {
    const email = event.data.customer.email;

    // Same email — TinyCRM upserts and updates status to paid
    crm.identify({
      email,
      status: "paid",
      params: {
        plan: event.data.items[0].price.name,
        mrr: event.data.items[0].recurring_totals.total / 100,
        converted_at: new Date().toISOString(),
      },
    });
  }

  return new Response("ok");
}

Result in TinyCRM dashboard

Before payment:  alex@example.com  │ [Product A: trial]  │ trial_started_at: ...
After payment:   alex@example.com  │ [Product A: paid]   │ plan: pro, mrr: 29

Frequently asked questions

Does TinyCRM track conversion rates?

TinyCRM tracks individual customer statuses, not aggregate metrics like conversion rate percentages. You can filter the customer table by status to count how many customers are in each stage, but TinyCRM is a customer database, not an analytics platform. Use the filtered table to identify specific customers to reach out to, not to produce funnel charts.

Can I filter customers by status?

Yes. The TinyCRM dashboard lets you filter the customer table by status — free, trial, paid, or churned — either globally across all projects or per-project. You can combine status filters with project filters and JSONB param filters to get very specific segments.

How do I update a customer's status?

Call identify() again with the same email and the new status. TinyCRM upserts on email — if the customer already exists for that project, it updates their status and params. A common pattern is to call identify() with status: 'trial' on sign-up, then call it again with status: 'paid' from your payment webhook handler.

Can I track different statuses per product?

Yes. Each customer-project relationship has its own independent status. A customer can be paid on Product A and still on trial for Product B. Both statuses are visible on the customer's unified profile row in the dashboard.

Know exactly who is on trial right now

14-day free trial. $9/month after. Takes 10 minutes to integrate.

Also see: All use cases · Next.js integration guide · Indie hacker customer retention