Self-hosted · your repo, your provider

Lifecycle email is a feature.
Ship it like one.

Welcome series, trial nudges, win-backs, payment saves — every product needs them. Hogsend turns PostHog and product events into journeys that live in your repo and send through your own Resend or Postmark account, working by this afternoon.

$ pnpm dlx create-hogsend@latest my-app

Free to self-host · No per-contact billing

Live demo

Put an email in.
The product does the rest.

This form feeds a stock create-hogsend app running in production. It ingests the event, runs its welcome journey, and the email arrives from hello@hogsend.com a few seconds later. A nudge follows in two days unless you deploy first.

Same engine, same journey code you scaffold · unsubscribe is one click

Works with

Journeys are TypeScript files in your repo. Everything else follows from that.

Built by a growth engineer after 15+ years of client work — and one too many hand-rolled lifecycle stacks. The story →

The problem

Mostpeoplewhosignupnevercomeback.Theproductwasfine;nothingaskedthemtoreturn.Thewelcome,thenudge,thewin-backhavesatatthebottomofthebacklogformonths.Hogsendshipsthelotinanafternoon,fromyourrepo.

The building blocks

Journeys and buckets, working together

Journeys are emails that play out over time — sleep, wait for what the user does next, and track opens and clicks as you go. Buckets are live groups of people. And every email and lifecycle event fans out to your destinations — PostHog, Segment, Slack, or any signed webhook.

Emails that play out over time

Trigger on an event, send, sleep, then branch on what happened while you waited. The control flow is plain TypeScript.

Trigger on eventsSleep & branchStop on conversion
export const welcome = defineJourney({
  meta: {
    id: "activation-welcome",
    trigger: { event: "user_signed_up" },
    entryLimit: "once",
  },
  run: async (user, ctx) => {
    await sendEmail({ to: user.email, template: "welcome" });
    await ctx.sleep({ duration: days(2) });
    const { found } = await ctx.history.hasEvent({
      userId: user.id,
      event: "feature_used",
    });
    if (!found) {
      await sendEmail({ to: user.email, template: "nudge" });
    }
  },
});
Agent-native

Agents can write all of it

Everyone bolted an MCP server onto their UI this year. Hogsend skipped the step. The whole surface is typed code in a repo, which is what agents are already best at.

src/journeys/winback.ts
export const winback = defineJourney({  meta: {    id: "winback",    trigger: { event: wentDormant.entered },    exitOn: [{ event: wentDormant.left }],  },  run: async (user, ctx) => {    await sendEmail({ /* check-in */ });    await ctx.sleep({ duration: days(7) });    await sendEmail({ /* final nudge */ });  },});

Journeys as code

Journeys are .ts files. Agents read them, write them, and open PRs against them. You review the diff like any other change.

Journey runtime
Live
  • Add a win-back journey — when someone enters the went-dormant bucket, send a check-in, wait 7 days, then a final nudge. Stop if they come back.
  • Created src/journeys/winback.ts — triggers on wentDormant.entered, exits on wentDormant.left, sends reactivation-checkin → ctx.sleep(days(7)) → reactivation-final-nudge. Ran `hogsend journeys --json` to verify — registered. Review the diff.

A CLI that speaks JSON

Every hogsend command takes --json — doctor, journeys, contacts, stats, events, webhooks. hogsend skills installs Claude Code skills into your repo, and /llms.txt gives every assistant the map.

Hogsend Studio — overview dashboard

Studio sees everything and changes nothing

Every send, journey run, and contact in one dashboard — preview templates with live props, resend a failed message, pause a sequence. Your editor stays the author.

How it works

The whole job is one loop

Activity comes in from PostHog or any webhook, the right emails go out through your provider, and what people do with them fans back out to your tools — PostHog, Segment, Slack, or anywhere. Nothing new to buy or keep in sync.

  1. Scaffold your app

    pnpm create hogsend@latest emits a thin app that pins @hogsend/engine and holds your content. Pass --domain to wire your sending domain from the start — sends redirect to your own inbox until the domain verifies.

    terminal
    # Scaffold a thin app that pins the engine$ pnpm create hogsend@latest my-app --domain mysite.com $ cd my-app$ hogsend dev   # infra, .env, migrate, API + worker → API on :3002 · Studio at /studio
  2. Define journeys & buckets

    TypeScript functions that trigger on events, send emails, wait, branch, and adapt.

    journeys/welcome.ts
    export const welcome = defineJourney({  meta: {    id: "activation-welcome",    trigger: { event: "user_signed_up" },    entryLimit: "once",  },  run: async (user, ctx) => {    await sendEmail({ to: user.email, template: "welcome" });    await ctx.sleep({ duration: days(2) });    const { found } = await ctx.history.hasEvent({      userId: user.id, event: "feature_used",    });    if (!found) await sendEmail({ template: "nudge" });  },});
  3. Deploy & watch it run

    Host with Docker or one-click Railway. Watch every send in Studio.

    deploy
    # One-click Railway template, or your own host$ git push origin main → building hogsend-api …→ building hogsend-worker …→ migrations applied · health check /v1/health ✓ # Watch every send in Studio
Growth, meet engineering

What growth can learn from engineering

Email tools never picked up the habits that make software dependable. Bring lifecycle email into the repo and it inherits all of them at once.

01Version control

Every change has a history

Edit a template in a dashboard and the old version is gone. Nobody can say what the welcome email said in March, who changed it, or why. In a repo every subject line has a diff and a way back.

02Code review

A second pair of eyes before it sends

Your welcome email gets read more often than your homepage. In most tools it goes live the moment someone clicks Save. In a repo it goes out through the same pull request as everything else you ship.

03Experiments

Tests you can still read next quarter

Most A/B tests survive as a memory of which variant won. The losing copy gets deleted and the reasoning lives in someone's head. When variants are code, the whole experiment stays on the record.

04Automation

Why click what you could type?

A canvas flow is forty drag-and-drops that nobody can review, reuse, or hand to an agent. The same logic is a dozen lines of TypeScript, and agents are already very good at writing those.

05Time to ship

Working by this afternoon

Most platforms want weeks of template building and flow clicking before the first send. The scaffold puts 10 journeys and 13 templates in your repo with one command, so the work starts at editing.

06Cost

Growth shouldn't be a billing event

Rented platforms meter contacts, which makes your growth their revenue. Self-hosted software costs the same at 50,000 contacts as it did at 500. Postgres has never charged anyone per row.

Journeys as code

Lifecycle logic is TypeScript in your repo — reviewed, type-checked, and versioned like the rest of your product.

Your provider, your reputation

Sends go through your own Resend or Postmark account — or any provider behind the EmailProvider contract.

First-party tracking

The engine rewrites links and tracks opens and clicks itself, whichever provider you plug in. The data lands in your own Postgres.

Durable execution

Journeys run as Hatchet durable tasks — a seven-day wait survives deploys, restarts, and crashes.

Powered by Hatchet

Durable execution, by Hatchet

Every journey runs on Hatchet, the durable execution engine underneath Hogsend. It's what lets a long ctx.sleep survive a deploy and resume two days later exactly where it left off, with retries and timeouts handled for you. Hogsend builds on Hatchet rather than rolling its own durability.

Survives deploys & restarts

A long ctx.sleep keeps running across a deploy and resumes days later, exactly where it left off.

Automatic retries & timeouts

Failed steps retry and waits expire on their own — durability you don't have to hand-roll.

Self-host it, or use Hatchet Cloud

Run Hatchet-Lite next to your app, or point at Hatchet Cloud. Same engine either way.

Economics

And nobody meters your contacts

Rent models are fine prices for software they host. Hogsend takes a different view: lifecycle email is a feature of your product, and features belong in your repo.

HogsendSelf-hosted
$0software
Charges byNothing — it's your infra.
When your list growsPostgres doesn't charge per row.
Loops
Charges bySubscribed contacts
When your list grows$249/mo at 50k contacts.*
Customer.io
Charges byProfiles + emails + credits
When your list growsTalk to sales.
PostHog Workflows
Charges by$0.003/send after 10k free/mo*
When your list growsPer-send creep.

*List prices at the time of writing — all pricing last checked June 2026.

Hogsend is free to self-host — run it commercially, deploy it for clients. It's your Postgres, your event log, your templates, your provider account — pg_dump is the exit interview. And if you'd rather have it installed for you, that's a week of work, done properly.

In the open

See for yourself

The engine on npm, the source on GitHub, a one-click deploy, and the engineer who built it — all a click away.

FAQ

Questions, answered

Still curious? The docs go deeper — including a side-by-side with PostHog Workflows.

Hogsend is source-available under the Elastic License 2.0 (ELv2), not an OSI-approved open-source license. You can read, modify, and self-host all of it for free; the only restriction is offering Hogsend itself as a managed service. All 11 packages are on npm and the full source is on GitHub.

Get started

One loop. Your repo.
Tonight.

One scaffold command sets up the app, Docker, env, and ten production journeys. Or deploy the Railway template in a click.

terminal
pnpm dlx create-hogsend@latest my-app

Free to self-host · PostHog + your provider · no contact tax

Get the changelog

Ships when something ships. No drip nonsense.