Trial emails driven by usage, not days remaining
“Your trial ends in 3 days” converts nobody who hasn't found value. Branch on what they did; stop the second they pay.
Free to self-host · One scaffold command · No per-contact billing
Every trial email tool can count down. The conversion question is behavioral: did they hit the milestone that predicts paying? If yes, ask early. If no, sell the milestone — not the deadline. That requires your email tool to see product usage, and to stop instantly when Stripe says they 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) {
// The upgrade ask, at the moment of value — not the deadline.
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.
Measure what matters
Branch on behavior, not opens
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
The short versions. The docs have the long ones.
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.
Same engine, different lifecycle stage
Trial emails that stop
when Stripe says stop
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

