{"id":50652463,"url":"https://github.com/dougwithseismic/hogsend","last_synced_at":"2026-06-10T09:00:50.283Z","repository":{"id":360207372,"uuid":"1248399024","full_name":"dougwithseismic/hogsend","owner":"dougwithseismic","description":"The lifecycle automation layer PostHog doesn't have yet. Code-first email journeys on PostHog + Resend. Self-hosted.","archived":false,"fork":false,"pushed_at":"2026-06-07T21:21:55.000Z","size":15459,"stargazers_count":3,"open_issues_count":48,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T21:24:03.288Z","etag":null,"topics":["email-automation","hatchet","lifecycle-automation","posthog","resend","self-hosted","typescript"],"latest_commit_sha":null,"homepage":"https://docs.hogsend.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dougwithseismic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-24T15:38:29.000Z","updated_at":"2026-06-07T20:53:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dougwithseismic/hogsend","commit_stats":null,"previous_names":["dougwithseismic/hogsend"],"tags_count":78,"template":false,"template_full_name":null,"purl":"pkg:github/dougwithseismic/hogsend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dougwithseismic%2Fhogsend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dougwithseismic%2Fhogsend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dougwithseismic%2Fhogsend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dougwithseismic%2Fhogsend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dougwithseismic","download_url":"https://codeload.github.com/dougwithseismic/hogsend/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dougwithseismic%2Fhogsend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34144680,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["email-automation","hatchet","lifecycle-automation","posthog","resend","self-hosted","typescript"],"created_at":"2026-06-07T21:00:31.270Z","updated_at":"2026-06-10T09:00:50.276Z","avatar_url":"https://github.com/dougwithseismic.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"hogsend-banner.png\" alt=\"Hogsend\" width=\"100%\" /\u003e\n\u003c/p\u003e\n\n# Hogsend\n\n[![CI](https://github.com/dougwithseismic/hogsend/actions/workflows/ci.yml/badge.svg)](https://github.com/dougwithseismic/hogsend/actions/workflows/ci.yml)\n\nThe lifecycle email automation that PostHog teams actually need. Code-first, self-hosted, source-available.\n\nPostHog tells you what users do. Resend delivers your emails. Hogsend is the bit in the middle — it listens for events, decides who gets what, waits, checks conditions, and sends. Journeys (email sequences) and buckets (real-time segments) are plain TypeScript functions, not YAML configs or drag-and-drop canvases — and because a user joining a bucket can itself trigger a journey, segmentation and messaging live in one event stream.\n\nPostHog + Resend is the lead wedge, not the cage: events come **in** from PostHog, signed presets for Stripe / Clerk / Supabase / Segment, your own app via the `@hogsend/client` SDK, or any custom webhook source — and engagement and lifecycle events fan **out** to PostHog, Segment, Slack, a CRM, or a warehouse. An inbound → react → outbound lifecycle layer.\n\nBuilt for small teams (1-10 eng) shipping product-led SaaS who picked PostHog and Resend and now need behavioral sequences without buying a third platform.\n\n**[Documentation](https://docs.hogsend.com)** | **[Getting Started](https://docs.hogsend.com/docs/getting-started)** | **[Integrations](https://docs.hogsend.com/docs/integrations)** | **[Recipes](https://docs.hogsend.com/docs/recipes)** | **[CLI Reference](https://docs.hogsend.com/docs/cli)** | **[Compare](https://docs.hogsend.com/docs/compare)**\n\nEverything ships on npm: scaffold an app with `pnpm create hogsend@latest --domain mysite.com`, self-host with Docker, or one-click on Railway.\n\n## Quickstart\n\n```bash\npnpm create hogsend@latest my-app --domain mysite.com\ncd my-app \u0026\u0026 pnpm hogsend dev\n```\n\nOne command boots the lot — infra, migrations, API, worker, and Studio at `localhost:3002/studio`. Sends are test-mode-safe from the first minute: every email redirects to your own inbox until `hogsend domain check` verifies your DNS (records printed for your DNS host, auto-applied on Cloudflare/Vercel when a token is set).\n\n\u003e Full guide: **[Getting Started](https://docs.hogsend.com/docs/getting-started)** | **[hogsend dev](https://docs.hogsend.com/docs/cli/dev)** | **[hogsend domain](https://docs.hogsend.com/docs/cli/domain)** | **[Test mode](https://docs.hogsend.com/docs/operating/test-mode)**\n\n\u003e **A note from Doug** — I built Hogsend to do more for my clients, faster. I kept rebuilding the same PostHog + Resend lifecycle plumbing for every team, so I built it properly once and opened it up for everyone. If you'd like a hand getting it running — PostHog setup, journeys, templates, deploy — I can have you live in days. It's source-available and yours to run solo; the offer to help is there if you want it.\n\u003e\n\u003e → **[About Hogsend \u0026 how to get in touch](https://docs.hogsend.com/docs/about)** — _Doug Silkstone_\n\n---\n\n## How It Works\n\nEvents flow in from PostHog, journeys react with emails via Resend, engagement data flows back. A closed loop — product analytics and lifecycle email in the same event stream.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"hogsend-lifecycle.png\" alt=\"PostHog Lifecycle Email Flow\" width=\"100%\" /\u003e\n\u003c/p\u003e\n\n\u003e Deep dive: **[How It Works](https://docs.hogsend.com/docs/concepts/how-it-works)** | **[Why PostHog?](https://docs.hogsend.com/docs/concepts/why-posthog)** | **[Why Hatchet?](https://docs.hogsend.com/docs/concepts/why-hatchet)** | **[Philosophy](https://docs.hogsend.com/docs/concepts/philosophy)**\n\n---\n\n## What You Can Build\n\n- **Welcome sequences** that branch based on whether the user actually used the product\n- **Trial-to-paid conversion** that watches for usage milestones and sends different emails depending on engagement\n- **Payment failure recovery** — escalating reminders that stop the moment the payment goes through\n- **Dormancy reactivation** — detect inactive users, run a win-back series, track if they come back\n- **NPS / feedback collection** timed after key moments\n- **Abandoned checkout recovery** — start a sequence when checkout begins, exit when it completes\n- **Cross-journey orchestration** — one journey enrolls a user in another, chaining sequences without duplicating logic\n- **Real-time segments** — group users the instant their behavior matches (power users, trials expiring, gone dormant), then trigger a journey off the membership change itself\n\nEach is a single TypeScript file using `defineJourney()` — or `defineBucket()` for segments. The repo ships with [10 production-ready journeys](apps/api/src/journeys/) and [3 example buckets](apps/api/src/buckets/) covering common lifecycle stages.\n\n## Buckets — real-time segments\n\nBuckets are the peer of journeys: named, code-defined groups a user **joins** the moment their data matches and **leaves** when it stops. Each join and leave fires an event through the same pipeline, so **a membership change can trigger a journey** — bind one to `bucketEntered(\"went-dormant\")` and it runs the instant someone goes dormant. You write a `defineBucket()` with criteria (the same condition engine, or a fluent builder) — no `run`, just the predicate. Membership recomputes in real time off your own event stream — the sub-hour, code-first complement to PostHog's ~24h batch cohorts — with a reconcile pass for time-based leaves and a `maxDwell` TTL.\n\n```ts\nimport { days, defineBucket } from \"@hogsend/engine\";\n\nexport const wentDormant = defineBucket({\n  meta: {\n    id: \"went-dormant\",\n    name: \"Went dormant\",\n    enabled: true,\n    timeBased: true,\n    criteria: (b) =\u003e\n      b.all(\n        b.event(\"app.active\").exists(), // was active at some point\n        b.event(\"app.active\").within(days(7)).notExists(), // but not lately\n      ),\n  },\n});\n```\n\n\u003e Full guide: **[Buckets](https://docs.hogsend.com/docs/guides/buckets)**\n\n---\n\n## Sources \u0026 destinations\n\nHogsend sits between whatever emits events and wherever you want them to land. Journeys and buckets are the reactive middle.\n\n**In** — events arrive from:\n\n- **PostHog** webhooks\n- **Signed presets** for **Stripe**, **Clerk**, **Supabase**, and **Segment** — auto-enabled the moment you set that preset's webhook secret\n- **Your own app** via the `@hogsend/client` SDK or a raw `POST /v1/events`\n- **Any custom source** you write with `defineWebhookSource()`\n\n**Out** — engagement and lifecycle events fan to:\n\n- **PostHog**, **Segment**, **Slack**, a **CRM**, a **warehouse**, or any signed webhook via `defineDestination()`\n- The outbound catalog is 13 events — `contact.*`, `email.*` (including `email.complained`), `journey.completed`, and `bucket.*`\n\nThe public data-plane API (`POST /v1/events`, contacts, lists, transactional email) is the front door for all of it — drive it from the typed `@hogsend/client` SDK or plain HTTP.\n\n\u003e Full guides: **[Integrations](https://docs.hogsend.com/docs/integrations)** | **[Recipes](https://docs.hogsend.com/docs/recipes)**\n\n---\n\n## Example Emails\n\nHogsend dogfoods itself. The example templates in [`apps/api/src/emails/`](apps/api/src/emails/) are real lifecycle emails _about_ Hogsend — built with [React Email](https://react.email) + Tailwind, sent through journeys defined in code. They're yours to edit, rebrand, or delete. Here's the set as it ships:\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eSetup guide\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eactivation-quickstart\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/activation-quickstart.png\" alt=\"Setup guide email — get your first journey live\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eNo events yet\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eactivation-nudge\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/activation-nudge.png\" alt=\"Activation nudge email — we haven't seen any events yet\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eJourneys as code\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eactivation-feature-highlight\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/activation-feature-highlight.png\" alt=\"Feature highlight email — journeys are just TypeScript\" width=\"100%\"/\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eWhat others build\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eactivation-community\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/activation-community.png\" alt=\"Community email — see what other teams ship\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eUsage milestone\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003econversion-usage-milestone\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/conversion-usage-milestone.png\" alt=\"Usage milestone email — 100 emails sent\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eTrial ending\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003econversion-trial-expiring\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/conversion-trial-expiring.png\" alt=\"Trial expiring email — your trial ends in 3 days\" width=\"100%\"/\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eWin-back offer\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003econversion-winback-offer\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/conversion-winback-offer.png\" alt=\"Win-back offer email — 20% off\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eMilestone unlocked\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eretention-achievement\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/retention-achievement.png\" alt=\"Achievement email — 10,000 emails delivered\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eWeekly digest\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003eretention-weekly-digest\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/retention-weekly-digest.png\" alt=\"Weekly digest email — your Hogsend week\" width=\"100%\"/\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eDormancy check-in\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003ereactivation-checkin\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/reactivation-checkin.png\" alt=\"Reactivation check-in email — your project's gone quiet\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eFinal nudge\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003ereactivation-final-nudge\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/reactivation-final-nudge.png\" alt=\"Final nudge email — we'll leave it here\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003eNPS survey\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003efeedback-nps-survey\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/feedback-nps-survey.png\" alt=\"NPS survey email — how are we doing\" width=\"100%\"/\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"33%\" valign=\"top\"\u003e\u003cb\u003ePayment failed\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e\u003ccode\u003echurn-payment-failed\u003c/code\u003e\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/emails/churn-payment-failed.png\" alt=\"Payment failed email — we couldn't process your payment\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\"\u003e\u003c/td\u003e\n    \u003ctd width=\"33%\"\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n---\n\n## Quick Example\n\nA `user_signed_up` event triggers this journey. It sends a welcome email, waits two days, checks if the user tried the core feature, and nudges them if not:\n\n```typescript\nimport { days, defineJourney, sendEmail } from \"@hogsend/engine\";\nimport { Events, Templates } from \"./constants/index.js\";\n\nexport const activationWelcome = defineJourney({\n  meta: {\n    id: \"activation-welcome\",\n    name: \"Activation — Welcome Series\",\n    enabled: true,\n    trigger: { event: Events.USER_CREATED },\n    entryLimit: \"once\",\n    exitOn: [{ event: Events.USER_DELETED }],\n  },\n\n  run: async (user, ctx) =\u003e {\n    await sendEmail({\n      to: user.email,\n      userId: user.id,\n      journeyName: user.journeyName,\n      template: Templates.ACTIVATION_WELCOME,\n      subject: \"Welcome — let's get you set up\",\n    });\n\n    await ctx.sleep({ duration: days(2), label: \"post-welcome\" });\n\n    const { found } = await ctx.history.hasEvent({\n      userId: user.id,\n      event: Events.FEATURE_USED,\n    });\n\n    if (!found) {\n      await sendEmail({\n        to: user.email,\n        userId: user.id,\n        journeyName: user.journeyName,\n        template: Templates.ACTIVATION_NUDGE,\n        subject: \"You haven't tried the key feature yet\",\n      });\n    }\n  },\n});\n```\n\nThat `ctx.sleep(days(2))` literally pauses for two days and picks up exactly where it left off — durable execution via [Hatchet](https://hatchet.run) that survives deploys and restarts. Need to wait on _behavior_ instead of the clock? `ctx.waitForEvent({ event: Events.FEATURE_USED, timeout: days(7) })` parks the journey until that user fires the event (or the timeout wins), then resumes — and an `exitOn` match cancels the wait mid-flight.\n\n\u003e Full guide: **[Journeys](https://docs.hogsend.com/docs/guides/journeys)** | **[Buckets](https://docs.hogsend.com/docs/guides/buckets)** | **[Events](https://docs.hogsend.com/docs/guides/events)** | **[Email](https://docs.hogsend.com/docs/guides/email)** | **[Conditions](https://docs.hogsend.com/docs/guides/conditions)**\n\n---\n\n## Get Started\n\nScaffold a fresh app with `create-hogsend`. It generates a thin app that pins `@hogsend/engine` and holds your content — journeys, email templates, webhook sources — then installs everything from npm:\n\n```bash\npnpm create hogsend@latest my-app --domain mysite.com\ncd my-app\n\npnpm bootstrap              # Docker, .env, Hatchet token, data-plane key, migrations\npnpm hogsend dev            # API + worker + Studio, one terminal (Ctrl+C stops it all)\n```\n\n`--domain` wires `EMAIL_FROM` + `EMAIL_DOMAIN` into the scaffold, and the create flow offers to run bootstrap for you. Prefer it by hand? `pnpm dev` + `pnpm worker:dev` in two terminals is the manual equivalent of `hogsend dev`'s run phase. Fire a `user.created` event (`pnpm hogsend dev --fire user.created --email you@example.com`) and watch the journey run. Upgrade the framework with `pnpm up \"@hogsend/*\"` — never a fork or a merge.\n\n\u003e Full guide: **[Installation](https://docs.hogsend.com/docs/getting-started/installation)** | **[Configuration](https://docs.hogsend.com/docs/getting-started/configuration)** | **[PostHog Setup](https://docs.hogsend.com/docs/getting-started/posthog-setup)**\n\n### The Hatchet token\n\nJourneys are durable, which is what lets `ctx.sleep(days(2))` pause for two days and survive deploys. That durability is backed by [Hatchet](https://hatchet.run), so you need a `HATCHET_CLIENT_TOKEN`. The local `docker compose` runs `hatchet-lite` for you — mint a token from its dashboard at [`localhost:8888`](http://localhost:8888), or use [Hatchet Cloud](https://cloud.onhatchet.run) if you'd rather not run it yourself.\n\n\u003e Full guide: **[Hatchet setup](https://docs.hogsend.com/docs/getting-started/hatchet)**\n\n### Deploy\n\nSame app, deployed your way. One-click on Railway, or self-host the full stack anywhere that runs Node.js + Postgres:\n\n[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/hogsend-posthog-audience-stack?referralCode=dougie)\n\n\u003e Full guide: **[Deploy on Railway](https://docs.hogsend.com/docs/operating/deploy-railway)** | **[Deploy with Docker](https://docs.hogsend.com/docs/operating/deploy-docker)**\n\n---\n\n## CLI\n\n`@hogsend/cli` is the agent-native companion — install it with `pnpm add -g @hogsend/cli`, or run any command through `pnpm dlx @hogsend/cli`. It talks to a running instance's admin API; pass `--json` for machine-readable output.\n\n```bash\nhogsend dev         # Run the full local stack: infra, API + worker, health, URLs\nhogsend domain      # Set up + verify the sending domain (DNS records, auto-apply)\nhogsend doctor      # Probe a running instance's health\nhogsend journeys    # List, inspect, enable, and disable journeys\nhogsend contacts    # List, inspect, and trace contact activity\nhogsend stats       # System-wide overview metrics\nhogsend events      # Stream a single user's event history\nhogsend setup       # Local onboarding — docker compose up, gen secret, db:migrate\nhogsend skills      # Install bundled Claude Code skills into .claude/skills\nhogsend eject       # Vendor a @hogsend/* package into vendor/\u003cname\u003e\nhogsend patch       # Patch a package via pnpm's native patch flow\n```\n\n\u003e Full reference: **[CLI Reference](https://docs.hogsend.com/docs/cli)**\n\n---\n\n## Studio\n\nHogsend ships with **Studio** — a read-and-operate admin UI for your instance. It's built to **observe, not author**: journeys and templates stay code-first, while the Studio shows you what's happening and gives you a few targeted actions (resend a failed send, enable/disable a journey, un-suppress a contact, send a test, manage API keys). It's a static SPA over the same `/v1/admin/*` API the [CLI](#cli) drives, so everything you can see is also scriptable.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"50%\" valign=\"top\"\u003e\u003cb\u003eSends\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eEvery email — filter, sort, and drill into the delivery + engagement timeline\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/studio/studio-sends.png\" alt=\"Hogsend Studio — email send log with delivery and engagement status\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"50%\" valign=\"top\"\u003e\u003cb\u003eJourneys\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eEnrollment and completion rates per journey, with an enable/disable toggle\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/studio/studio-journeys.png\" alt=\"Hogsend Studio — per-journey completion rates and funnels\" width=\"100%\"/\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"50%\" valign=\"top\"\u003e\u003cb\u003eTemplates\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eThe template catalog with a live preview, per-template stats, and send-test\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/studio/studio-templates.png\" alt=\"Hogsend Studio — template catalog with live preview and stats\" width=\"100%\"/\u003e\u003c/td\u003e\n    \u003ctd width=\"50%\" valign=\"top\"\u003e\u003cb\u003eOverview\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eDelivery and engagement metrics for the whole instance at a glance\u003c/sub\u003e\u003cbr/\u003e\u003cimg src=\"apps/docs/public/images/studio/studio-overview.png\" alt=\"Hogsend Studio — overview metrics dashboard\" width=\"100%\"/\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nThe engine serves the built Studio at **`/studio`** on your API — same origin, so it uses your session cookie and needs no extra config (in the dogfood monorepo, build it once with `pnpm --filter @hogsend/studio build`). Or drive any instance from your machine with the CLI:\n\n```bash\nhogsend studio --open                                   # serve locally, open browser\nhogsend studio --base-url https://api.example.com --open\n```\n\n\u003e Full guide: **[Studio](https://docs.hogsend.com/docs/operating/studio)** | **[hogsend studio](https://docs.hogsend.com/docs/cli/studio)**\n\n---\n\n## Reporting\n\nEvery send is recorded — template, recipient, journey, and the full engagement trail (delivered, opened, clicked, bounced, complained) — and it's all queryable over the admin API. No external analytics, no ETL; the numbers are SQL aggregates computed on demand.\n\n```bash\n# What was sent to one user, oldest first\ncurl -H \"Authorization: Bearer $ADMIN_API_KEY\" \\\n  \"$API/v1/admin/emails?userId=user_abc123\u0026sort=createdAt\u0026order=asc\"\n\n# Opened emails from a specific journey\ncurl -H \"Authorization: Bearer $ADMIN_API_KEY\" \\\n  \"$API/v1/admin/emails?journeyId=activation-welcome\u0026engagement=opened\"\n\n# One send's full delivery timeline (queued → sent → delivered → opened → clicked)\ncurl -H \"Authorization: Bearer $ADMIN_API_KEY\" \"$API/v1/admin/emails/$ID\"\n\n# Per-template performance over a window\ncurl -H \"Authorization: Bearer $ADMIN_API_KEY\" \\\n  \"$API/v1/admin/metrics/emails?from=2026-05-01T00:00:00Z\"\n```\n\n- **`GET /v1/admin/emails`** — filter by `templateKey`, `category`, `status`, `journeyId`, `userId`, `engagement` (opened/clicked/bounced/complained), and a date window; sort by any lifecycle timestamp. Each row resolves who it went to and from which journey.\n- **`GET /v1/admin/emails/{id}`** — a single send with a chronological `events[]` timeline and every tracked-link click (URL, IP, user agent).\n- **`GET /v1/admin/metrics/emails`** — per-template `sent`/`delivered`/`opened`/`clicked`/`bounced` with delivery, open, click, and click-to-delivery rates over an optional window.\n\n\u003e Full guide: **[Email Operations](https://docs.hogsend.com/docs/operating/emails)** | **[Metrics \u0026 Analytics](https://docs.hogsend.com/docs/operating/metrics)** | **[API Reference](https://docs.hogsend.com/docs/api/emails)**\n\n---\n\n## Stack\n\n| Concern | Tool |\n|---------|------|\n| HTTP API | Hono on Node.js |\n| Durable execution | Hatchet (sleeps, retries, event routing) |\n| Database | TimescaleDB (Postgres 18) via Drizzle ORM |\n| Cache | Redis |\n| Email delivery | Resend by default (`@hogsend/plugin-resend`) — swappable: Postmark via `@hogsend/plugin-postmark`, or any provider implementing the contract |\n| Product analytics | PostHog (`@hogsend/plugin-posthog`) |\n| Email templates | React Email |\n| CLI | TypeScript on Node (`@hogsend/cli`) |\n| Deploy | Railway (one-click), Docker Compose, or bring-your-own |\n\nPlugins are standalone packages — create your own for Slack, Twilio, or any service. See **[Creating Plugins](https://docs.hogsend.com/docs/guides/plugins)**.\n\nThe email provider is the one swappable wire. Resend is the default; Postmark ships as an opt-in (`pnpm add @hogsend/plugin-postmark@latest`, then set `EMAIL_PROVIDER=postmark`), and SES-style providers slot in behind the same `EmailProvider` contract. Tracking, preferences, and delivery records stay engine-owned, so first-party open/click tracking works no matter which provider you wire in.\n\n---\n\n## Explore the Docs\n\n| Section | What's there |\n|---------|-------------|\n| **[Getting Started](https://docs.hogsend.com/docs/getting-started)** | Installation, PostHog setup, configuration reference |\n| **[Concepts](https://docs.hogsend.com/docs/concepts/how-it-works)** | How it works, why PostHog, why Hatchet, philosophy |\n| **[Compare](https://docs.hogsend.com/docs/compare)** | Hogsend vs Customer.io, Loops, Brevo, ActiveCampaign — feature matrix and migration |\n| **[Building](https://docs.hogsend.com/docs/guides/journeys)** | Journeys, buckets, events, email, conditions, creating plugins |\n| **[CLI Reference](https://docs.hogsend.com/docs/cli)** | Every command documented with examples |\n| **[Operating](https://docs.hogsend.com/docs/operating)** | Deployment, auth, monitoring, metrics, bulk ops, troubleshooting |\n| **[API Reference](https://docs.hogsend.com/docs/api)** | Every endpoint with request/response examples |\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code style, and how to submit changes.\n\n## License\n\n[Elastic License 2.0 (ELv2)](LICENSE) — use, modify, and self-host freely. You can't offer it as a managed service or remove license key functionality. See [LICENSE](LICENSE) for full terms.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdougwithseismic%2Fhogsend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdougwithseismic%2Fhogsend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdougwithseismic%2Fhogsend/lists"}