Guides / SaaS

Multi-Channel Event Strategy for Growing SaaS

4 min read·SaaS

The Problem with Ad-Hoc Channels

Most teams start with signups and payments and add channels as they think of them. After 6 months they have 30 channels with overlapping concerns, inconsistent naming, and Smart Actions rules that are hard to reason about.

A deliberate channel strategy prevents this.

The Four-Domain Model

Organize channels into four domains — each with a clear owner and purpose:

Revenue Domain

payments          # Successful charges, payment intents
payment-failures  # Declined charges, failed intents
subscriptions     # Trial starts, conversions, upgrades, downgrades
churn             # Cancellations, subscription deletions
refunds           # Refunds and chargebacks
disputes          # Stripe/payment disputes

Engagement Domain

signups           # Account creation
activation        # First value milestones (see activation guide)
feature-usage     # Key feature interactions
onboarding        # Onboarding step completions
emails            # Transactional email sent/bounced/opened

Operations Domain

deployments       # Production deploys
errors            # Application errors and exceptions
cron              # Scheduled job results
jobs              # Background worker events
api-usage         # API call volume (if applicable)
security          # Auth failures, suspicious activity

Infrastructure Domain

db-queries        # Slow query alerts
uptime            # Health check results
performance       # Core Web Vitals, API latency spikes

Channel Naming Conventions

ts
// ✅ Good channel names
"payments"
"payment-failures"
"trial-signups"
"api-usage"

// ❌ Avoid
"payment_failures"    // inconsistent casing
"all-events"          // too broad
"payments-v2"         // versioning in channel names
"misc"                // a dumping ground

Wiring It Up

ts
// lib/events.ts — centralized event logging
import { logit } from "@/lib/logit";

export const events = {
  signups: {
    userSignedUp: (email: string, plan: string) =>
      logit.now("signups", { event: "New user signed up", description: email, icon: "👤", notify: true, tags: { plan } }),
  },
  payments: {
    paymentReceived: (email: string, amount: number, currency: string) =>
      logit.now("payments", { event: "Payment received", description: `${email}${(amount/100).toFixed(2)} ${currency.toUpperCase()}`, icon: "💰", notify: true, tags: { currency } }),
    paymentFailed: (email: string, reason: string) =>
      logit.now("payment-failures", { event: "Payment failed", description: `${email}${reason}`, icon: "❌", notify: false, tags: { reason } }),
  },
  ops: {
    deploySucceeded: (sha: string, actor: string) =>
      logit.now("deployments", { event: "Production deployment succeeded", description: `${sha.slice(0, 7)} by ${actor}`, icon: "🚀", notify: true, tags: { actor } }),
  },
};

Tips

  • Create a central lib/events.ts wrapper so channel names and event names are defined in one place, not scattered across every file.
  • Review your channel list every quarter and merge overlapping channels.
  • Build Smart Actions rules after the channel structure is stable — rules that reference poorly-named channels are hard to maintain.

Try LogIt free

7-day trial. No credit card required.

Start free