Trial emails that check usage first
This journey waits three days, checks whether the user hit a usage milestone, and sends the upgrade ask only if they did. When the Stripe subscription.created event arrives, exitOn cancels the journey — even mid-wait.
Free to self-host · One scaffold command · No per-contact billing
Every trial email tool can count down. The signal that predicts paying is behavioral: did this user hit the activation milestone? Branching on that means the email tool has to see product usage — and it has to stop the moment the Stripe webhook says they've paid.
Branch on usage; exit on payment
It mirrors the trial-upgrade journey that ships in the scaffold.
import { days } from "@hogsend/core";
import { defineJourney, sendEmail } from "@hogsend/engine";
export const trialConversion = defineJourney({
meta: {
id: "trial-conversion",
name: "Trial conversion",
enabled: true,
trigger: { event: "trial.started" },
entryLimit: "once",
exitOn: [
// The built-in Stripe preset feeds this in — the journey is
// cancelled the moment they pay, even mid-wait.
{ event: "subscription.created" },
{ event: "user.deleted" },
],
},
run: async (user, ctx) => {
await ctx.sleep({ duration: days(3), label: "usage-check" });
const { found: hitMilestone } = await ctx.history.hasEvent({
userId: user.id,
event: "usage.milestone_reached",
});
if (hitMilestone) {
// They've found value — ask while it's fresh.
await sendEmail({
to: user.email,
userId: user.id,
journeyStateId: user.stateId,
template: "conversion-usage-milestone",
subject: "You're on a roll — here's what the paid plan unlocks",
journeyName: user.journeyName,
});
}
await ctx.sleep({ duration: days(7), label: "trial-ending" });
await sendEmail({
to: user.email,
userId: user.id,
journeyStateId: user.stateId,
template: "conversion-trial-expiring",
subject: "Your trial ends in 3 days — don't lose your progress",
journeyName: user.journeyName,
});
},
});exitOn: subscription.created cancels the journey mid-wait — nobody gets a “trial ending!” email after their card was charged.
For “3 days before expiry at their 9am”, swap the fixed sleep for ctx.sleepUntil(...) with ctx.when — the timezone-aware fluent scheduler that respects your configured send window. See the journeys guide.
The same mechanics, executing
A conversion journey running: trigger, a short wait, the usage check coming back empty, and the recovery email going out.
Measure what matters
Branch on signals you can trust
Clicks and conversion events are reliable; opens are directional — Apple Mail Privacy Protection inflates them. The milestone event is the signal worth branching on.
Conversions, via PostHog
Conversion events fan out to PostHog — and on to Meta or Google via PostHog's Destinations pipeline. See conversion tracking.
Chain journeys with ctx.trigger
ctx.trigger chains converts straight into onboarding and non-converts into win-back.
The emails it sends ship with the scaffold
All 13 templates are React Email components in your repo. These three carry the conversion sequence.
Questions, answered
Short answers here; the docs go deeper.
Go deeper
Declare exitOn: [{ event: "subscription.created" }] in the journey meta. The built-in Stripe webhook preset feeds the event in, and Hogsend cancels the journey immediately — even if it's mid-wait.
Trial emails that stop on payment
The scaffold ships 10 journeys and 13 templates to start from. Wire the Stripe preset and the upgrade sequence is one reviewable file.
Free to self-host · One scaffold command · No per-contact billing
pnpm dlx create-hogsend@latest my-app

