Hogsend is brand new.Try it
Hogsend
Use case: trial conversion

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

The countdown problem

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.

The journey

Branch on usage; exit on payment

It mirrors the trial-upgrade journey that ships in the scaffold.

src/journeys/trial-conversion.ts
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 run

The same mechanics, executing

A conversion journey running: trigger, a short wait, the usage check coming back empty, and the recovery email going out.

src/journeys/abandoned-checkout.ts
export const checkout = defineJourney({
meta: { trigger: { event: Events.CHECKOUT_ABANDONED } },
run: async (user, ctx) => {
await ctx.sleep({ duration: hours(2) });
const { found } = await ctx.history.hasEvent({
event: Events.CHECKOUT_COMPLETED,
within: hours(2),
});
if (found) return;
await sendEmail({ template: "need-help" });
},
});
The run
eventcheckout.abandoned · doug@hogsend.comenrolled
sleep
2 hours
checkcheckout_completed · within 2hfound: false
pricing.vieweddocs.opened
send
Need help with anything?deliveredopenedclicked
Measurement

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.

Templates

The emails it sends ship with the scaffold

All 13 templates are React Email components in your repo. These three carry the conversion sequence.

FAQ

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

terminal
pnpm dlx create-hogsend@latest my-app