Hogsend is lifecycle automation in TypeScript for product-led teams. Written by you—or your coding agent. Reviewed in a PR. Shipped like the rest of your product.
Onboarding. Trial conversion. Payment recovery. Retention. Win-back. Across email, in-app, SMS, Discord, and more.
Your product already emits the signals. Hogsend turns them into durable journeys that wait, branch, send, and stop based on what each customer does—or does not do. Journeys and real-time segments are plain TypeScript functions, not YAML configs or drag-and-drop canvases.
Send first-party events directly with @hogsend/js or @hogsend/client, or bring Stripe, PostHog, Segment, and any webhook source. Fan the results back to analytics, Slack, a CRM, or your warehouse. PostHog works beautifully with Hogsend, but it is optional.
Built for technical founders and product engineers who want to ship the customer lifecycle as fast as they ship the product.
Live demo | Documentation | Getting Started | Integrations | Recipes | CLI Reference | Compare
Everything ships on npm: scaffold an app with pnpm create hogsend@latest --domain mysite.com, self-host with Docker, or one-click on Railway.
Live demo — demo.hogsend.com is a fully-seeded Studio you can click around; it runs "Forgeline", a fictional credit-based dev-tool SaaS. Use the one-click Enter the demo button, or sign in with
demo@hogsend.com/forgeline-demo-2026.
pnpm create hogsend@latest my-app --domain mysite.com
cd my-app && pnpm hogsend devOne command boots the lot — infra, migrations, API, worker, and Studio at localhost:3002/studio. Sends are test-mode-safe from the first minute: every email redirects to your own inbox until hogsend domain check verifies your DNS (records printed for your DNS host, auto-applied on Cloudflare/Vercel when a token is set).
Full guide: Getting Started | hogsend dev | hogsend domain | Test mode
A note from Doug — I built Hogsend to do more for my clients, faster. I kept rebuilding the same PostHog + Resend lifecycle plumbing for every team, so I built it properly once and opened it up for everyone. If you'd like a hand getting it running — PostHog setup, journeys, templates, deploy — I can have you live in days. It's source-available and yours to run solo; the offer to help is there if you want it.
→ About Hogsend & how to get in touch — Doug Silkstone
Events arrive from your product, Stripe, PostHog, or any webhook. Durable journeys react across your providers, then engagement and lifecycle outcomes flow back onto the same contact and event stream.
PostHog is a first-class optional integration: use the events and identities you already capture, then send opens, clicks, answers, and lifecycle outcomes back to it. Without PostHog, Hogsend’s own SDKs and contact model run the same lifecycle loop.
Deep dive: How It Works | Why PostHog? | Why Hatchet? | Philosophy
- Welcome sequences that branch based on whether the user actually used the product
- Trial-to-paid conversion that watches for usage milestones and sends different emails depending on engagement
- Payment failure recovery — escalating reminders that stop the moment the payment goes through
- Dormancy reactivation — detect inactive users, run a win-back series, track if they come back
- NPS / feedback collection timed after key moments
- Abandoned checkout recovery — start a sequence when checkout begins, exit when it completes
- Cross-journey orchestration — one journey enrolls a user in another, chaining sequences without duplicating logic
- Real-time segments — group users the instant their behavior matches (power users, trials expiring, gone dormant), then trigger a journey off the membership change itself
Each is a single TypeScript file using defineJourney() — or defineBucket() for segments. The repo ships with 10 production-ready journeys and 3 example buckets covering common lifecycle stages.
Buckets are the peer of journeys: named, code-defined groups a user joins the moment their data matches and leaves when it stops. Each join and leave fires an event through the same pipeline, so a membership change can trigger a journey — bind one to bucketEntered("went-dormant") and it runs the instant someone goes dormant. You write a defineBucket() with criteria (the same condition engine, or a fluent builder) — no run, just the predicate. Membership recomputes in real time off your own event stream — the sub-hour, code-first complement to PostHog's ~24h batch cohorts — with a reconcile pass for time-based leaves and a maxDwell TTL.
import { days, defineBucket } from "@hogsend/engine";
export const wentDormant = defineBucket({
meta: {
id: "went-dormant",
name: "Went dormant",
enabled: true,
timeBased: true,
criteria: (b) =>
b.all(
b.event("app.active").exists(), // was active at some point
b.event("app.active").within(days(7)).notExists(), // but not lately
),
},
});Full guide: Buckets
Agents can prototype journeys before they become code. A Journey Blueprint is a lifecycle journey authored as a JSON graph, stored in the database (journey_blueprints), and run by the engine's generic interpreter — it goes live the moment you enable it, no deploy. Blueprints run through the same enrollment guards, durable execution, and terminal states as code journeys; they provide a fast staging path for an agent or the admin API.
Author them with the MCP server (@hogsend/mcp, the manage_blueprint tool) or the /v1/admin/blueprints API; Studio observes them and toggles enable/disable (it doesn't author). When one has proven itself, hogsend blueprints promote turns it into a real defineJourney() file and freezes the blueprint. The committed code becomes the source of truth.
Full guide: Journey Blueprints | MCP server
Hogsend sits between whatever emits events and wherever you want them to land. Journeys and buckets are the reactive middle.
In — events arrive from:
- Your own app via
@hogsend/js, the@hogsend/clientSDK, or a rawPOST /v1/events - Signed presets for Stripe, Clerk, Supabase, and Segment — auto-enabled the moment you set that preset's webhook secret
- PostHog webhooks, when PostHog is part of your stack
- Any custom source you write with
defineWebhookSource()
Out — engagement and lifecycle events fan to:
- PostHog, Segment, Slack, a CRM, a warehouse, or any signed webhook via
defineDestination() - The outbound catalog is 13 events —
contact.*,email.*(includingemail.complained),journey.completed, andbucket.*
Web, email, server, and Discord activity for one human resolve to one Hogsend contact. When PostHog is configured, Hogsend also stitches those identities forward onto the same PostHog person.
The public data-plane API (POST /v1/events, contacts, lists, transactional email) is the front door for all of it — drive it from the typed @hogsend/client SDK or plain HTTP.
Full guides: Integrations | Recipes
Hogsend dogfoods itself. The example templates in apps/api/src/emails/ are real lifecycle emails about Hogsend — built with React Email + Tailwind, sent through journeys defined in code. They're yours to edit, rebrand, or delete. Here's the set as it ships:
A user_signed_up event triggers this journey. It sends a welcome email, waits two days, checks if the user tried the core feature, and nudges them if not:
import { days, defineJourney, sendEmail } from "@hogsend/engine";
import { Events, Templates } from "./constants/index.js";
export const activationWelcome = defineJourney({
meta: {
id: "activation-welcome",
name: "Activation — Welcome Series",
enabled: true,
trigger: { event: Events.USER_CREATED },
entryLimit: "once",
exitOn: [{ event: Events.USER_DELETED }],
},
run: async (user, ctx) => {
await sendEmail({
to: user.email,
userId: user.id,
journeyName: user.journeyName,
template: Templates.ACTIVATION_WELCOME,
subject: "Welcome — let's get you set up",
});
await ctx.sleep({ duration: days(2), label: "post-welcome" });
const { found } = await ctx.history.hasEvent({
userId: user.id,
event: Events.FEATURE_USED,
});
if (!found) {
await sendEmail({
to: user.email,
userId: user.id,
journeyName: user.journeyName,
template: Templates.ACTIVATION_NUDGE,
subject: "You haven't tried the key feature yet",
});
}
},
});That ctx.sleep(days(2)) literally pauses for two days and picks up exactly where it left off — durable execution via Hatchet that survives deploys and restarts. Need to wait on behavior instead of the clock? ctx.waitForEvent({ event: Events.FEATURE_USED, timeout: days(7) }) parks the journey until that user fires the event (or the timeout wins), then resumes — and an exitOn match cancels the wait mid-flight.
Full guide: Journeys | Buckets | Events | Email | Conditions
Scaffold a fresh app with create-hogsend. It generates a thin app that pins @hogsend/engine and holds your content — journeys, email templates, webhook sources — then installs everything from npm:
pnpm create hogsend@latest my-app --domain mysite.com
cd my-app
pnpm bootstrap # Docker, .env, Hatchet token, data-plane key, migrations
pnpm hogsend dev # API + worker + Studio, one terminal (Ctrl+C stops it all)--domain wires EMAIL_FROM + EMAIL_DOMAIN into the scaffold, and the create flow offers to run bootstrap for you. Prefer it by hand? pnpm dev + pnpm worker:dev in two terminals is the manual equivalent of hogsend dev's run phase. Fire a user.created event (pnpm hogsend dev --fire user.created --email you@example.com) and watch the journey run. Upgrade the framework with pnpm up "@hogsend/*" — never a fork or a merge.
Full guide: Installation | Configuration | PostHog Setup
Journeys are durable, which is what lets ctx.sleep(days(2)) pause for two days and survive deploys. That durability is backed by Hatchet, so you need a HATCHET_CLIENT_TOKEN. The local docker compose runs hatchet-lite for you — mint a token from its dashboard at localhost:8888, or use Hatchet Cloud if you'd rather not run it yourself.
Full guide: Hatchet setup
Same app, deployed your way. One-click on Railway, or self-host the full stack anywhere that runs Node.js + Postgres:
Full guide: Deploy on Railway | Deploy with Docker
@hogsend/cli is the agent-native companion — install it with pnpm add -g @hogsend/cli, or run any command through pnpm dlx @hogsend/cli. It talks to a running instance's admin API; pass --json for machine-readable output.
hogsend dev # Run the full local stack: infra, API + worker, health, URLs
hogsend domain # Set up + verify the sending domain (DNS records, auto-apply)
hogsend doctor # Probe a running instance's health
hogsend journeys # List, inspect, enable, and disable journeys
hogsend contacts # List, inspect, and trace contact activity
hogsend stats # System-wide overview metrics
hogsend events # Stream a single user's event history
hogsend setup # Local onboarding — docker compose up, gen secret, db:migrate
hogsend skills # Install bundled Claude Code skills into .claude/skills
hogsend eject # Vendor a @hogsend/* package into vendor/<name>
hogsend patch # Patch a package via pnpm's native patch flowFull reference: CLI Reference
Hogsend ships with Studio — a read-and-operate admin UI for your instance. It's built to observe, not author: journeys and templates stay code-first, while the Studio shows you what's happening and gives you a few targeted actions (resend a failed send, enable/disable a journey, un-suppress a contact, send a test, manage API keys). It's a static SPA over the same /v1/admin/* API the CLI drives, so everything you can see is also scriptable.
The engine serves the built Studio at /studio on your API — same origin, so it uses your session cookie and needs no extra config (in the dogfood monorepo, build it once with pnpm --filter @hogsend/studio build). Or drive any instance from your machine with the CLI:
hogsend studio --open # serve locally, open browser
hogsend studio --base-url https://api.example.com --openFull guide: Studio | hogsend studio
Hogsend Desktop is a native macOS menubar companion. It polls your instance's /v1/health for a live status glyph (🟢/🟡/🔴), raises native notifications on send/journey failures or a worker going offline, and opens the real Studio in a native window — it embeds Studio, it's not a fork. Add one or more instances by URL and switch between them; optionally store credentials in the macOS Keychain for auto-login, and let the built-in updater keep it current.
Download the universal (Apple Silicon + Intel) .dmg from GitHub Releases: Hogsend.dmg. It isn't notarized yet, so on first launch clear the quarantine flag once with xattr -dr com.apple.quarantine /Applications/Hogsend.app (or right-click → Open); after that, auto-updates are seamless.
Full guide: Desktop app | All releases
Every send is recorded — template, recipient, journey, and the full engagement trail (delivered, opened, clicked, bounced, complained) — and it's all queryable over the admin API. No external analytics, no ETL; the numbers are SQL aggregates computed on demand.
# What was sent to one user, oldest first
curl -H "Authorization: Bearer $ADMIN_API_KEY" \
"$API/v1/admin/emails?userId=user_abc123&sort=createdAt&order=asc"
# Opened emails from a specific journey
curl -H "Authorization: Bearer $ADMIN_API_KEY" \
"$API/v1/admin/emails?journeyId=activation-welcome&engagement=opened"
# One send's full delivery timeline (queued → sent → delivered → opened → clicked)
curl -H "Authorization: Bearer $ADMIN_API_KEY" "$API/v1/admin/emails/$ID"
# Per-template performance over a window
curl -H "Authorization: Bearer $ADMIN_API_KEY" \
"$API/v1/admin/metrics/emails?from=2026-05-01T00:00:00Z"GET /v1/admin/emails— filter bytemplateKey,category,status,journeyId,userId,engagement(opened/clicked/bounced/complained), and a date window; sort by any lifecycle timestamp. Each row resolves who it went to and from which journey.GET /v1/admin/emails/{id}— a single send with a chronologicalevents[]timeline and every tracked-link click (URL, IP, user agent).GET /v1/admin/metrics/emails— per-templatesent/delivered/opened/clicked/bouncedwith delivery, open, click, and click-to-delivery rates over an optional window.
Full guide: Email Operations | Metrics & Analytics | API Reference
| Concern | Tool |
|---|---|
| HTTP API | Hono on Node.js |
| Durable execution | Hatchet (sleeps, retries, event routing) |
| Database | TimescaleDB (Postgres 18) via Drizzle ORM |
| Cache | Redis |
| Email delivery | Resend by default (@hogsend/plugin-resend) — swappable: Postmark via @hogsend/plugin-postmark, or any provider implementing the contract |
| Product analytics | PostHog (@hogsend/plugin-posthog) |
| Email templates | React Email |
| CLI | TypeScript on Node (@hogsend/cli) |
| MCP server | @hogsend/mcp — stdio (npx @hogsend/mcp) + hosted POST /v1/mcp, for Claude Desktop / Cursor / claude.ai |
| Deploy | Railway (one-click), Docker Compose, or bring-your-own |
Plugins are standalone packages — create your own for Slack, Twilio, or any service. See Creating Plugins.
The email provider is the one swappable wire. Resend is the default; Postmark ships as an opt-in (pnpm add @hogsend/plugin-postmark@latest, then set EMAIL_PROVIDER=postmark), and SES-style providers slot in behind the same EmailProvider contract. Tracking, preferences, and delivery records stay engine-owned, so first-party open/click tracking works no matter which provider you wire in.
| Section | What's there |
|---|---|
| Getting Started | Installation, PostHog setup, configuration reference |
| Concepts | How it works, why PostHog, why Hatchet, philosophy |
| Compare | Hogsend vs Customer.io, Loops, Brevo, ActiveCampaign — feature matrix and migration |
| Building | Journeys, buckets, events, email, conditions, creating plugins |
| CLI Reference | Every command documented with examples |
| Operating | Deployment, auth, monitoring, metrics, bulk ops, troubleshooting |
| API Reference | Every endpoint with request/response examples |
See CONTRIBUTING.md for setup, code style, and how to submit changes.
Elastic License 2.0 (ELv2) — use, modify, and self-host freely. You can't offer it as a managed service or remove license key functionality. See LICENSE for full terms.


















