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/engineplus the other@hogsend/*packages) is the framework. Upgrade it withpnpm 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
- Scaffold your app —
pnpm dlx create-hogsend@latest my-appemits a thin app that pins@hogsend/engineand holds your content. - Events flow in — Send them from
@hogsend/js, callPOST /v1/eventsfrom your server, point a PostHog webhook at the app, or wire anydefineWebhookSource()(Stripe, Clerk, your CRM). They all normalize to one event for a user throughingestEvent(). - Your code reacts — TypeScript functions that trigger on events, send emails, wait, branch, and adapt based on what the user does.
- Email sends through a swappable provider — Resend by default, Postmark opt-in, or any other behind the
EmailProvidercontract. React Email templates with first-party open/click tracking, unsubscribe management, and deliverability monitoring — all engine-owned, so they come along with any provider. - 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 terminalAccept 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
.envfrom.env.examplewith a freshly generatedBETTER_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_TOKENfor 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/clientwork out of the box, and a full-adminHOGSEND_ADMIN_KEYthat powers thehogsendCLI.
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/engineis a semver-stable API surface. Upgrade withpnpm up "@hogsend/*"and run migrations. - Code-first journeys —
defineJourney()with durable execution backed by Hatchet. Yourawait ctx.sleep({ duration: days(3) })literally pauses for three days and picks up exactly where it left off — orctx.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
EmailProvidercontract. 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/eventsor 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/dband gates boot; your own client schema is yours to evolve. Both run from a singlepnpm 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.