Hogsend is brand new.Chat to Doug
Self-hosted · your repo, your provider

The lifecycle email layer
PostHog doesn't have yet

An event comes in — from PostHog, Stripe, or your own app. A TypeScript journey in your repo reacts: it sends the welcome, waits two days, checks what the user did, and branches. Every send, open, and click fans back out to PostHog. Email goes through your own Resend or Postmark account.

$ pnpm dlx create-hogsend@latest my-app

Free to self-host · No per-contact billing

Live demo

The product, running on itself

Tell it a few things about you. Drop your email at the end and a stock create-hogsend app running in production ingests the event, runs its welcome journey, and sends from hello@hogsend.com a few seconds later. A nudge follows two days on.

What brings you here?

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

PostHogisincredibleatshowingyouwhereusersdropoff.Actingonitthewelcome,thenudge,thewin-backhasmeantbuyingasecondplatformandsyncingyourdataintoit.Hogsendisthatlayerascode:TypeScriptjourneysinyourrepo,triggeredbytheeventsyoualreadyhave.

The code

Pick a use case, read the journey

Each flow is one TypeScript file — trigger, durable waits, branches. Swap the provider underneath; the journey doesn't change.

src/journeys/onboarding.ts
import { days } from "@hogsend/core";
import { defineJourney, sendEmail } from "@hogsend/engine";

export const onboarding = defineJourney({
  meta: {
    id: "onboarding",
    trigger: { event: "user.signed_up" },
    entryLimit: "once",
    exitOn: [{ event: "user.deleted" }],
  },
  run: async (user, ctx) => {
    await sendEmail({ to: user.email, template: "activation-quickstart" });

    // Park durably until THIS user creates a project — or 3 days pass.
    const { timedOut } = await ctx.waitForEvent({
      event: "project.created",
      timeout: days(3),
    });

    await sendEmail({
      to: user.email,
      template: timedOut ? "activation-nudge" : "activation-feature-highlight",
    });
  },
});
.env
# provider is config, not journey code
EMAIL_PROVIDER=resend
RESEND_API_KEY=re_…

Shortened from the full journey — the API is real, the provider lives in config.

Read the use case
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

The whole surface is typed code in a repo, which is the interface agents already have. An agent writes the journey file, your type-checker validates it, and hogsend journeys --json confirms it registered.

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 is the read side

Every send, journey run, and contact in one dashboard — template previews with live props, resend and pause controls. Journey edits happen in your editor and ship through review.

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
Why a repo

What the repo gives you

Lifecycle email in a repo inherits the habits that make software dependable.

01Version control

Every change has a history

Every template and journey has a git history. What the welcome email said in March is one git log away, with who changed it and why.

02Code review

The same pull request as everything else

A journey ships through the same pull request as the rest of your product. Nothing goes live because someone clicked Save.

03Experiments

Finished tests stay on the record

Variants are code, so finished A/B tests stay in history — the losing copy, the reasoning, the result.

04Automation

A dozen lines instead of a canvas

A canvas flow is forty drag-and-drops; the same logic is a dozen lines of TypeScript that fit in a diff.

05Time to ship

The work starts at editing

The scaffold puts 10 journeys and 13 templates in your repo with one command. The work starts at editing, not building.

06Cost

Costs scale with infrastructure

Self-hosted software costs the same at 50,000 contacts as at 500. Costs scale with your infrastructure, not your list.

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, clicks, and in-email answers 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

What it costs

There is no paid tier. You pay for hosting — the Railway template provisions Postgres, Redis, Hatchet, the API, and the worker — and for your own Resend or Postmark account. That's the entire cost structure; contact count appears in neither bill.

HogsendSelf-hosted
$0software
Charges byNothing — it's your infra.
When your list growsSame software, same infra bill.
Loops
Charges bySubscribed contacts
When your list grows$249/mo at 50k contacts.*
Customer.io
Charges byProfiles + emails + credits
When your list growsCustom pricing at scale.
PostHog Workflows
Charges by$0.003/send after 10k free/mo*
When your list growsCosts scale with send volume.

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

Hogsend is free to self-host under ELv2 — run it commercially, deploy it for clients. Contacts, events, and templates live in your own Postgres and your own repo, so leaving means taking your database with you. If you'd rather have it installed for you: one week, $2,300, deployed on your infrastructure with your first journeys live.

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.

Built for

Whose problem this solves

Pick your seat — one sentence on what Hogsend gives it.

Lifecycle email in the repo you already have — no second platform to buy and babysit.

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

First send in minutes

The scaffold command sets up the app, Docker, env, and ten journeys — the welcome series included. pnpm bootstrap brings the stack up. Or deploy the Railway template in a click.

terminal
pnpm dlx create-hogsend@latest my-app

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