Hogsend is brand new.Try it
Hogsend

Introduction

Lifecycle automation in TypeScript for product-led teams. Build onboarding, conversion, retention, and win-back journeys in your repo.

What is Hogsend?

Your customer lifecycle belongs in your repo.

Hogsend is lifecycle automation in TypeScript for product-led teams. It is one loop: an inbound event comes in, your code reacts, engagement flows back out. A call from your own app, a Stripe event, a PostHog webhook — they all normalize to an event for a user. Your TypeScript reacts (journeys send/wait/branch, buckets segment in real time), and everything that happens — opens, clicks, sends, journey completions — fans back out to your analytics, CRM, or warehouse.

Journeys are written by you—or your coding agent—then reviewed in a PR, tested, versioned, and shipped like the rest of your product. Not drag-and-drop canvases. Code.

Install the engine, own your content

Hogsend ships as an npm package — @hogsend/engine — that your app installs like any other framework. You scaffold a fresh app that pins the engine and owns only your content: journeys, email templates, webhook sources, custom routes, config, and your own database migrations.

  • The engine (@hogsend/engine plus the other @hogsend/* packages) is the framework. Upgrade it with pnpm up "@hogsend/*"; when you need to go further than config, you Extend, Patch, or Eject.
  • Your content lives in your repo and is injected into the engine's factories (createHogsendClient, createApp, createWorker). The engine never imports your content.

Why Hogsend exists

Acquisition fills the bucket. Weak activation, stalled trials, failed payments, disengagement, and missed follow-up empty it. Your product already emits the signals, but acting on them usually means cron jobs, webhook glue, or a second platform that drifts away from the product.

Hogsend closes that response gap. Scaffold the framework, ship your first journeys, and improve activation, conversion, and retention with the same workflow you use to improve the product.

First-party by default; PostHog when you want it

@hogsend/js and @hogsend/client give your product its own event and identity spine. Stripe webhooks, custom API calls, and any system that can send an HTTP request can feed the same stream. PostHog is a first-class optional integration: bring the events and identities you already capture, and fan lifecycle outcomes back into your analytics.

The email provider is swappable — Resend is the default (fast, developer-friendly, great deliverability). Postmark ships as an opt-in provider, and you can bring any other behind the EmailProvider contract — render, preferences, and first-party tracking come along no matter which wire you choose. The same lifecycle event stream fans out to Slack, PostHog, Segment, a CRM, or a warehouse through outbound destinations. Push notifications are on the roadmap.

How it works

  1. Scaffold your apppnpm dlx create-hogsend@latest my-app emits a thin app that pins @hogsend/engine and holds your content.
  2. Events flow in — Send them from @hogsend/js, call POST /v1/events from your server, point a PostHog webhook at the app, or wire any defineWebhookSource() (Stripe, Clerk, your CRM). They all normalize to one event for a user through ingestEvent().
  3. Your code reacts — TypeScript functions that trigger on events, send emails, wait, branch, and adapt based on what the user does.
  4. Email sends through a swappable providerResend by default, Postmark opt-in, or any other behind the EmailProvider contract. React Email templates with first-party open/click tracking, unsubscribe management, and deliverability monitoring — all engine-owned, so they come along with any provider.
  5. Engagement flows back out — Opens, clicks, sends, journey completions, and bucket transitions fan out to PostHog, Segment, Slack, a CRM, or a warehouse via outbound destinations.

Get started in minutes

Scaffold a fresh app — local setup runs as part of the scaffold — then start the dev stack:

pnpm dlx create-hogsend@latest my-app
cd my-app
pnpm hogsend dev   # API + worker + Studio on :3002, one terminal

Accept the defaults at every prompt (Docker must be installed and running) and the scaffolder does the whole first run for you. Its setup step is the same script as pnpm bootstrap — idempotent, safe to re-run any time. In one pass it:

  • Checks Docker is installed and the daemon is running.
  • Creates .env from .env.example with a freshly generated BETTER_AUTH_SECRET.
  • Resolves ports — auto-remaps any busy host port (Postgres, Redis, Hatchet, the app itself) to the next free one and writes them back to .env.
  • Starts containers — TimescaleDB, Redis, and hatchet-lite via Docker Compose.
  • Auto-mints your Hatchet token — no bring-your-own-token step locally; it sets HATCHET_CLIENT_TOKEN for you. (Bring your own only in production / Hatchet Cloud.)
  • Runs migrations — both the engine track and your client track — and verifies the schema reached HEAD.
  • Mints two API keys — an ingest-scoped HOGSEND_API_KEY (hsk_…) so the data API and @hogsend/client work out of the box, and a full-admin HOGSEND_ADMIN_KEY that powers the hogsend CLI.

Set RESEND_API_KEY in .env before sending real email — everything else is wired for you. Driving it from an agent or CI? pnpm dlx create-hogsend@latest my-app --yes scaffolds, installs, and sets up in a single hands-off step with an honest exit code (or pass . instead of a name to scaffold into the current folder). The Hatchet dashboard runs at http://localhost:8888 (admin@example.com / Admin123!!).

To log into Studio, create the first admin first. Public sign-up is disabled, so the first admin is minted from your server — run pnpm studio:admin (which calls hogsend studio admin create), or set STUDIO_ADMIN_EMAIL (+ optional STUDIO_ADMIN_PASSWORD) and the API mints it on boot into an empty user table. There is no web sign-up form.

Quick example

A user_signed_up event from your app or analytics source triggers this journey. It sends a welcome email, waits a day, checks if the user tried the core feature, and nudges them if not. Journey files import from the environment-free @hogsend/engine/journeys surface so the same function can run in a unit test; app and worker entry points use the main @hogsend/engine runtime surface.

import { days, defineJourney, sendEmail } from "@hogsend/engine/journeys";

export const onboarding = defineJourney({
  meta: {
    id: "onboarding-welcome",
    name: "Onboarding — Welcome Series",
    enabled: true,
    trigger: { event: "user_signed_up" },
    entryLimit: "once",
    suppress: days(1),
  },

  run: async (user, ctx) => {
    await sendEmail({
      to: user.email,
      userId: user.id,
      template: "activation/welcome",
      subject: "Welcome — let's get you set up",
      journeyName: user.journeyName,
    });

    await ctx.sleep({ duration: days(1), label: "post-welcome" });

    const { found } = await ctx.history.hasEvent({
      userId: user.id,
      event: "feature_used",
    });

    if (!found) {
      await sendEmail({
        to: user.email,
        userId: user.id,
        template: "activation/nudge",
        subject: "You haven't tried the key feature yet",
        journeyName: user.journeyName,
      });
    }
  },
});

You register this journey by adding it to the journeys array your app exports from src/journeys/index.ts — the array you pass to createHogsendClient({ journeys }) and createWorker({ container, journeys }). No YAML, no state machines, no visual canvas. Just TypeScript with if, await, and loops — running as durable tasks that survive restarts and deploys.

What you get

  • A versioned engine@hogsend/engine is a semver-stable API surface. Upgrade with pnpm up "@hogsend/*" and run migrations.
  • Code-first journeysdefineJourney() with durable execution backed by Hatchet. Your await ctx.sleep({ duration: days(3) }) literally pauses for three days and picks up exactly where it left off — or ctx.waitForEvent() pauses until the user actually does something (with a timeout fallback), so journeys react to behavior, not just the clock.
  • First-party event SDKs — Send typed product events directly with @hogsend/js, @hogsend/client, or the public data API.
  • Optional PostHog integration — Point a PostHog webhook at your app and events flow through automatically; send lifecycle outcomes back through an outbound destination.
  • Swappable email delivery — Resend by default, Postmark opt-in, or bring your own behind the EmailProvider contract. React Email templates, tracked sends, bounce handling, one-click unsubscribe, preference center — all engine-owned, so they come along with any provider.
  • A public data plane — Write contacts, events, and transactional emails from your own code with one ingest-scoped key via POST /v1/events or the typed @hogsend/client.
  • Outbound destinations — Fan the lifecycle event stream out to PostHog, Segment, Slack, a CRM, or a warehouse over the durable webhook spine with defineDestination().
  • Composable conditions — Property checks, event history, email engagement, AND/OR composition. Use them for enrollment guards, exit conditions, and mid-journey branching.
  • Two-track migrations — Engine schema ships upstream in @hogsend/db and gates boot; your own client schema is yours to evolve. Both run from a single pnpm db:migrate.
  • Admin API and Studio — The same operational surface powers the CLI and visual Studio for contacts, journey control, email metrics, alerting, and audit logs.
  • Self-hosted — Deploy to Railway (or anywhere that runs Node.js + Postgres). Your data stays yours.

Next steps