Hogsend
Getting Started

Getting Started

Scaffold a fresh Hogsend app, run it locally against Docker, and watch your first journey fire — from zero to a sending pipeline in a few minutes.

Hogsend is a code-first lifecycle orchestration engine: an event comes in, your code reacts, engagement flows back out. You define journeys in TypeScript, connect them to events (PostHog is the standard source, but any system that can send an HTTP request works), and Hogsend handles the durable execution — email sequences, timing, branching, and exit conditions. Email sends through a swappable provider, Resend by default.

The framework ships as an npm package, @hogsend/engine. You scaffold a fresh app that owns content only — journeys, email templates, webhook sources, custom routes, and your own database migrations — and pins @hogsend/engine. The engine never imports your content; upgrading the framework is pnpm up. See How It Works for the full mental model.

The spine

Two commands take you from nothing to a running, sending app:

pnpm create hogsend@latest my-app --domain mysite.com   # scaffold + wire your sending domain
cd my-app
pnpm hogsend dev                                        # infra + .env + migrate + API + worker, one command

--domain writes EMAIL_FROM=hello@mysite.com + EMAIL_DOMAIN=mysite.com into the scaffold's env, and the create flow offers to run local setup (Docker, Hatchet token, data-plane key, migrations) for you. hogsend dev then runs everything from one terminal — and prints the URLs when the API is healthy, including Studio at http://localhost:3002/studio. (hogsend is the bundled @hogsend/cli; pnpm add -g @hogsend/cli if you'd rather drop the pnpm prefix.)

Fire an event and a journey runs:

pnpm hogsend dev --fire test.signup --email you@example.com   # from a second terminal

Then watch it move through Studio. Installation walks the whole loop end to end, including your first journey.

Sends are safe by default. With HOGSEND_TEST_MODE=auto (the default), every email is redirected to your own inbox — subject prefixed [TEST → real@recipient] — until your sending domain verifies. You can exercise real journeys and real sends from minute one without a single message reaching a customer. See Test mode and hogsend domain.

Prefer the pieces one at a time? pnpm bootstrap (Docker + .env + Hatchet token + migrate) followed by pnpm dev and pnpm worker:dev in two terminals is the manual equivalent — both paths are covered below.

Step 1 — scaffold

create-hogsend copies a fresh content-only app and pins the current engine version:

pnpm create hogsend@latest my-app --domain mysite.com

--domain wires your sending domain at scaffold time — EMAIL_FROM=hello@mysite.com + EMAIL_DOMAIN=mysite.com land in env.example (and the bootstrap-copied .env inherits them). Skip it and the env keeps a commented placeholder block to fill in later. With --domain and no app name, the name defaults to the first domain label (mysite).

Use . to scaffold into the current folder, and pass --pm npm|yarn|bun to switch package managers (default pnpm). Run it with no app name in a terminal for an interactive setup, or pass --yes to accept every default — scaffold, install, and bootstrap — with no prompts:

pnpm create hogsend@latest my-app --domain mysite.com --yes

The scaffold installs dependencies for you and initializes a git repo. It also drops a .claude/skills/ tree and a tailored CLAUDE.md so Claude Code can author journeys, emails, and buckets with you (skip with --no-skills).

Step 2 — bootstrap

From the app directory, one command sets up everything you need to run locally:

pnpm bootstrap

bootstrap is idempotent and safe to re-run. It runs seven steps and does the setup work for you:

  • Checks Docker is installed and the daemon is running.
  • Creates .env from .env.example and generates a fresh BETTER_AUTH_SECRET.
  • Resolves ports — if 5434, 6380, 8888, or 7077 are taken, it remaps to free ports and writes them back to .env.
  • Starts containers — TimescaleDB (Postgres 18), Redis, and Hatchet-Lite via docker compose up -d --wait.
  • Auto-mints the Hatchet token — it mints a HATCHET_CLIENT_TOKEN against your local Hatchet-Lite and writes it to .env. You do not bring your own token for local dev.
  • Runs migrations — both the engine track and your client track.
  • Mints the data-plane key — an ingest-scoped HOGSEND_API_KEY (hsk_…) is generated, stored as a hash, and written to .env. This is the key the @hogsend/client SDK and the hogsend CLI use to call your data plane.

Locally, the Hatchet token is auto-minted by bootstrap. The bring-your-own-token contract applies only to production — Hatchet Cloud or a self-hosted engine. See Get a Hatchet token for the production path.

When it finishes, your stack is up and the Hatchet dashboard is at http://localhost:8888 (login admin@example.com / Admin123!!). On every day after the first, hogsend dev reuses all of this — it detects the running containers and the minted credentials, and just runs the app.

Step 3 — create the first Studio admin

Studio is login-only — public sign-up is disabled, and there is no web create-admin form. The first admin is minted from your server, in one of two ways:

  • The bootstrap prompt. After migrations, pnpm bootstrap offers an interactive "create your first Studio admin" step (it's skippable, and a no-op in CI / non-TTY or when STUDIO_ADMIN_EMAIL is already set).
  • By hand, any time. Run the scaffold's script, which loads .env and calls the CLI:
pnpm studio:admin   # → hogsend studio admin create

The CLI writes directly to the database (it needs DATABASE_URL + BETTER_AUTH_SECRET from the environment — no HTTP, no running API). Prefer the masked password prompt over --password, which can leak into shell history. On a deploy, set STUDIO_ADMIN_EMAIL (+ optional STUDIO_ADMIN_PASSWORD) instead and the API mints the admin on boot into an empty user table. Either way, the password goes only through Better Auth's hasher — never plaintext, never logged. Locked out later? hogsend studio admin reset. See Operating → Studio.

Step 4 — run

One command runs the whole local stack:

pnpm hogsend dev

hogsend dev detects the infra bootstrap brought up (skipping docker compose up when it's already running), checks .env, runs migrations, spawns the API and the worker as prefixed child processes, waits for GET /v1/health, and prints the local URLs — API, Studio, Hatchet dashboard. Ctrl+C stops everything.

The api and worker are two separate processes that share the same codebase: the api serves HTTP and pushes events to Hatchet; the worker executes durable tasks (email sends, journey orchestration, background jobs). Prefer to run them by hand? Each in its own terminal:

pnpm dev          # HTTP API on http://localhost:3002
pnpm worker:dev   # Hatchet worker (second terminal)

In development the api serves interactive docs at http://localhost:3002/docs (Scalar UI) and the OpenAPI spec at /openapi.json. Health is at GET /v1/health. Both are disabled when NODE_ENV=production.

The first journey to edit is src/journeys/welcome.ts. Fire an event at it and watch the worker pick it up — hogsend dev --fire from a second terminal, or the raw curl:

pnpm hogsend dev --fire user.created --email you@example.com
# equivalent:
curl -X POST http://localhost:3002/v1/events \
  -H "Authorization: Bearer $HOGSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "user.created", "email": "you@example.com" }'

Installation covers this end to end, including authoring the journey itself.

Step 5 — verify your domain

Until your sending domain verifies, test mode redirects every email to your own inbox — so nothing above could reach a real customer. When you're ready to send live, verify the domain from the CLI (it talks to your running instance's admin API; you'll need an admin key — see connecting):

pnpm hogsend domain status              # where am I? (state, records, test-mode banner)
pnpm hogsend domain add mysite.com      # register with the provider, print DNS records
pnpm hogsend domain check               # poll every 15s until verified

add detects where your DNS lives (Cloudflare, Vercel, Route 53, GoDaddy, Namecheap, Porkbun, Google Domains) via NS lookup, prints the records formatted for that host's panel with a deep link — and on Cloudflare or Vercel it offers to apply the records for you when a CLOUDFLARE_API_TOKEN / VERCEL_TOKEN is set. check polls until verified; test mode auto-exits within 60 seconds of DNS verifying, no restart needed. Full reference: hogsend domain.

Prerequisites

  • Node.js 22 — pinned via the scaffold's .node-version.
  • pnpm (recommended) — or npm/yarn/bun via the --pm flag.
  • Docker installed and running — for Timescale, Redis, and Hatchet-Lite. bootstrap provisions all three.
  • An email provider (needed to verify a domain and send real mail — test mode works before that) — Hogsend sends through a swappable provider, Resend by default: sign up free, grab your API key (starts with re_), and set a real RESEND_API_KEY in .env. Postmark ships as an opt-in alternative, and any other works behind the EmailProvider contract. The dependency-free smoke test in Installation needs no provider at all.
  • PostHog account (optional for first boot) — with a project API key (phc_) if you want PostHog to drive your events and person properties.

PostHog is the standard event source, but it's not required to boot. You can drive Hogsend entirely through the data plane — POST /v1/events and the @hogsend/client SDK — from your own app code, webhook sources, or any of the built-in integrations. Lifecycle events fan back out to PostHog, Segment, or Slack via outbound destinations.

Next steps

On this page