| name | hogsend-authoring-journeys |
| description | Use when adding or editing a lifecycle journey in src/journeys/ — wiring a defineJourney() trigger/entryLimit/exitOn/suppress, writing the run(user, ctx) control flow, durable sleeps, branching on history/engagement, sending email from a journey, and the register-in-index + thread-into-client/worker ritual. |
| license | MIT |
| metadata | {"author":"withSeismic","version":"1.2.0"} |
Authoring Hogsend journeys
A journey is a code-first lifecycle flow. You declare a defineJourney({ meta, run }) in src/journeys/: meta says who enters and when they exit, and
run(user, ctx) is plain TypeScript control flow — send email, durably sleep,
branch on history. Each journey compiles to its own Hatchet durable task, so the
worker can restart mid-flow and resume exactly where it left off.
You are editing a scaffolded consumer app (content only). Journey files
import definitions, types, duration helpers, and effects from the
environment-free @hogsend/engine/journeys entry point. Reserve the main
@hogsend/engine entry for runtime bootstrap files that create the app, client,
or worker. Never touch engine internals.
Anatomy of a journey
import { days, defineJourney, hours, sendEmail } from "@hogsend/engine/journeys";
import { Events, Templates } from "./constants/index.js";
export const welcome = defineJourney({
meta: {
id: "welcome",
name: "Welcome Series",
enabled: true,
trigger: { event: Events.USER_CREATED },
entryLimit: "once",
suppress: hours(12),
exitOn: [{ event: Events.USER_DELETED }],
},
run: async (user, ctx) => {
await sendEmail({
to: user.email,
userId: user.id,
journeyStateId: user.stateId,
template: Templates.ACTIVATION_WELCOME,
subject: "Welcome — let's get you set up",
journeyName: user.journeyName,
});
await ctx.sleep({ duration: days(2), label: "post-welcome" });
const { found } = await ctx.history.hasEvent({
userId: user.id,
event: Events.FEATURE_USED,
});
if (!found) {
await sendEmail({ });
}
},
});
Key concepts
ctx is orchestration primitives ONLY — sleep, sleepUntil, when,
waitForEvent, digest, throttle, checkpoint, trigger, once,
variant, guard.isSubscribed, history.hasEvent/journey/email. ctx.digest
collapses a window of trigger events into ONE run (batch via Object.groupBy
over digest.events); ctx.throttle is an advisory windowed send-count
check; ctx.variant(key, arms) is the deterministic RECORDED A/B arm (pure
sha256, equal split, replayed verbatim within an enrollment).
Features are standalone imports: sendEmail() comes from
@hogsend/engine/journeys, NOT off ctx.
- Fan-out is DESTINATIONS, not
ctx. There is no ctx.identify /
ctx.posthog.capture — those single-vendor PostHog shims were removed. To get
user/event data into product + data tools (PostHog, Segment, Slack, a CRM, a
warehouse), set up an outbound DESTINATION: the email/contact/journey/bucket
lifecycle is delivered there durably (retry/backoff/DLQ), keyed by
webhook_endpoints.kind. EVERY destination receives EVERY open and click
(per-hit, not first-touch), and email.delivered is the canonical "email was
received" signal. See the hogsend-authoring-destinations skill.
- Duration helpers
days() / hours() / minutes() from
@hogsend/engine/journeys — never magic strings.
user carries id, email, properties, stateId, journeyId,
journeyName — pass user.stateId to sendEmail so the send is attributed.
- Constants
Events / Templates live in your src/journeys/constants/.
Templates keys must match a key in src/emails/ registry.
Task playbooks — load the matching reference
- Shape
meta (trigger, entryLimit, exitOn, suppress) + understand the
enrollment gates and state transitions → references/journey-meta.md
- The full
ctx primitive API and what is deliberately NOT on it →
references/journey-context.md
- Send an email from inside
run → references/sending-email-from-a-journey.md
- Branch after a sleep on engagement / history, idempotently →
references/branch-on-engagement.md
- Register a new journey: export + thread into client/worker + ENABLED_JOURNEYS
→
references/register-a-journey.md
For trigger.where / exitOn[].where property conditions and the duration
helpers in depth, see the hogsend-conditions skill. To verify a journey runs
against a live instance (enroll a test user, watch it complete), see the
hogsend-cli skill.