The components, wired to your journeys
The in-app feed, bell, survey card and preference center are real @hogsend/react + @hogsend/js components — drop them into your app. Email, Discord, Telegram and PostHog ride the same journeys and one identity. One theming surface across all of it.
Drop them into your app
Knock-grade in-app messaging as React components, on a zero-dependency browser client. Every interaction is a first-party event your journeys trigger on — inapp.item_clicked, inapp.preference_changed, a survey answer read by ctx.waitForEvent — not just analytics.
One provider, then import what you need
The kit is the feed, the bell, the in-feed survey card and the preference center — plus a banner and a toast. Import the ones you need; a hooks-only import pulls no CSS. The bell ↗ in this nav is the same NotificationBell, live.
import {
HogsendProvider,
NotificationBell,
NotificationFeed,
} from "@hogsend/react";
import { PreferenceCenter } from "@hogsend/react/preferences";
import "@hogsend/react/styles.css";
// One provider, one publishable key — every component reads from it.
export function Inbox() {
return (
<HogsendProvider apiUrl={API_URL} publishableKey="pk_live_…">
<NotificationBell onClick={toggle} />
<NotificationFeed feedId="in_app" />
<PreferenceCenter />
</HogsendProvider>
);
}Fires demo.survey. The NPS card lands in the feed below — answer it and a journey drops the thank-you item.
import { days } from "@hogsend/core";
import { defineJourney, sendFeedItem, sendSurvey } from "@hogsend/engine";
import { Events } from "./constants/index.js";
// demo.survey → drop an in-app NPS card the visitor answers in their bell.
export const demoSurvey = defineJourney({
meta: {
id: "demo-survey",
name: "Demo — In-app survey",
enabled: true,
trigger: { event: Events.DEMO_SURVEY }, // "demo.survey"
entryLimit: "unlimited", // re-fire freely
suppress: days(0),
},
run: async (user) => {
await sendSurvey({
recipient: { anonymousId: user.id }, // your canonical key
event: Events.DEMO_NPS_SUBMITTED, // "demo.nps_submitted"
mode: "nps",
property: "score", // the answer rides here
prompt: "How likely are you to recommend Hogsend?",
title: "Quick question 👇",
minLabel: "Not likely",
maxLabel: "Very likely",
});
// sendSurvey has NO journeyStateId option — replay-safety is auto-keyed off
// the Hatchet run anchor inside sendFeedItem.
},
});
// demo.nps_submitted → a thank-you item echoing the score (closes the loop).
export const demoNpsAnswered = defineJourney({
meta: {
id: "demo-nps-answered",
name: "Demo — NPS answered → thank-you",
enabled: true,
trigger: { event: Events.DEMO_NPS_SUBMITTED }, // "demo.nps_submitted"
entryLimit: "unlimited",
suppress: days(0),
},
run: async (user) => {
const raw = user.properties.score; // SurveyBlockView captured it under "score"
const score =
typeof raw === "number" || typeof raw === "string" ? String(raw) : "?";
await sendFeedItem({
recipient: { anonymousId: user.id },
type: "survey-thanks",
title: `Thanks — you scored ${score} 🙏`,
body: "You answered an in-app survey. That emitted demo.nps_submitted onto the spine — a journey read your score and dropped this.",
actionUrl: "https://hogsend.com/docs/client-side/survey",
journeyStateId: user.stateId, // sendFeedItem DOES accept this
});
},
});Toasts, re-skinned with tokens
Toast and ToastContainer are in the kit too. Pop one and recolor it live — the same --hs-* token surface that themes the feed, the bell and the survey card.
Fires useToast().show() — it renders bottom-right via <ToastContainer>, re-skinned by the --hs-* tokens you pick. No CVA, no design lock-in.
Theme with tokens
Re-skin every component from one --hs-* block. No CVA, no Tailwind required — the whole kit reads CSS variables.
Style any slot
Per-slot classNames and data-* state attributes on every element, so it matches your design system exactly.
Or replace it wholesale
renderItem, renderHeader and asChild hand you the markup. The closed-loop events still fire, so a custom UI can't opt out.
Email that asks, and listens
Templates are React components in your repo. The links inside are events, the opens and clicks are first-party, and the provider is your own.
The click is the answer
EmailAction renders a link whose click means something. A yes/no is two of them, an NPS is eleven — each answer fires a real event with its payload, and the journey branches on it. First click per send wins; scanner bursts are filtered before anything is recorded.
The channels they already live in
Link a member's Discord or Telegram to their contact, and their activity there becomes a journey trigger — a reaction, a server join, a message — on the same identity as their email and product events.
Discord
/link confirms an email — folds a member's Discord and email onto one contact and grants the verified role. Reactions, joins and messages trigger journeys; a journey can grant roles and post back.
Telegram
A t.me/<bot>?start= link or /link binds Telegram to a contact. Messages trigger journeys, and a journey can reply in the same chat.
PostHog, both directions
hogsend connect posthog opens one browser consent. Person reads resolve timezones and property conditions; every email and lifecycle event fans back into PostHog as a captured event; and cross-channel ids — Discord, Telegram, email — fold into one person.
Every component, one scaffold
The scaffold puts the components, the journeys, and the email templates in a repo you own — wired to one engine. Import the first component in your editor.
pnpm dlx create-hogsend@latest my-appFree to self-host · One scaffold command · No per-contact billing