Onboarding that reacts to product events
A timed drip emails everyone the same, whatever they did. This journey waits for the user to create a project — up to three days — then sends one of two emails depending on whether it happened.
Free to self-host · One scaffold command · No per-contact billing
A timed sequence sends “how's it going?” to someone who's been in the product all morning, and “just checking in!” to someone who churned at signup. Both are the same bug: the sequence can't see product events, and your product events are in PostHog. With the events in hand, the branch is an ordinary `if` on what the user did.
The whole sequence is one file
It mirrors the welcome journey that ships in the scaffold — trigger, durable wait, branch, exit.
import { days } from "@hogsend/core";
import { defineJourney, sendEmail } from "@hogsend/engine";
export const onboarding = defineJourney({
meta: {
id: "onboarding",
name: "Onboarding",
enabled: true,
trigger: { event: "user.signed_up" },
entryLimit: "once",
exitOn: [{ event: "user.deleted" }],
},
run: async (user, ctx) => {
await sendEmail({
to: user.email,
userId: user.id,
journeyStateId: user.stateId,
template: "activation-quickstart",
subject: "Welcome — here's the shortest path to a first win",
journeyName: user.journeyName,
});
// Park the journey — durably — until THIS user creates a project,
// or 3 days pass. Survives deploys, restarts, and crashes.
const { timedOut } = await ctx.waitForEvent({
event: "project.created",
timeout: days(3),
});
await sendEmail({
to: user.email,
userId: user.id,
journeyStateId: user.stateId,
template: timedOut ? "activation-nudge" : "activation-feature-highlight",
subject: timedOut
? "Stuck? Here's the 2-minute version"
: "Nice — here's what to try next",
journeyName: user.journeyName,
});
await ctx.sleep({ duration: days(4), label: "pre-community" });
// Re-check after the long wait — unsubscribes don't exit a journey.
if (await ctx.guard.isSubscribed()) {
await sendEmail({
to: user.email,
userId: user.id,
journeyStateId: user.stateId,
template: "activation-community",
subject: "Join the community",
journeyName: user.journeyName,
});
}
},
});Trigger, durable wait, branch — one file, one reviewable diff.
The same file, executing
Enrol, send, a durable wait that resolves when the project lands, branch, write-back to PostHog.
Why it holds up
ctx.waitForEvent
Parks the journey durably until the user acts — or doesn't. The branch is an if statement.
exitOn
Removes anyone who deletes their account mid-sequence, even mid-wait.
Entry limits
entryLimit: "once" means re-signups don't get re-welcomed.
ctx.when
Times any send to 9am in their timezone, inside your send window — timezone auto-resolved from PostHog person properties, then the contact, then your default.
The emails it sends ship with the scaffold
All 13 templates are React Email components in your repo. These four cover activation — edit them like any other component.
Questions, answered
Short answers here; the docs go deeper.
Go deeper
Point a PostHog webhook destination at your Hogsend ingest endpoint; the user.signed_up event enrolls users in the journey automatically. Signup events can also come from Clerk or Supabase via built-in signed presets, or from your own backend via the Data API.
Onboarding your team can review
The scaffold ships 10 journeys and 13 templates to start from, including a welcome sequence shaped like this one.
Free to self-host · One scaffold command · No per-contact billing
pnpm dlx create-hogsend@latest my-app


