Hogsend is brand new.Try it
Campaigns

One-off sends to your whole audience

A campaign sends one template to every subscribed member of a list — or every active member of a bucket — at an instant you pick. Commit it as a file or queue it with one API call. Scheduled, cancelable until send, deduplicated per recipient.

One file · Ships on deploy · Cancel until it sends

The problem

Not everything is triggered. Journeys cover email that reacts to what a user did — sign-up, trial ending, going quiet. A launch, a changelog, a pricing change reacts to nothing: it goes to everyone on a list, once, at a time you choose. In most stacks that send lives in a dashboard, outside the repo that owns the rest of your email. Campaigns put the one-off send in the same place as your journeys — code.

Authoring

A committed file, or one API call

Both create the same campaign: the same row, the same durable send.

src/campaigns/product-launch.ts
import { defineCampaign } from "@hogsend/engine";

export const productLaunch = defineCampaign({
  id: "product-launch",
  audience: { list: "product-updates" },
  template: "marketing/product-update",
  props: {
    headline: "Saved views are here",
    ctaUrl: "https://example.com/changelog",
  },
  sendAt: "2026-07-15T16:00:00Z",
});

The worker picks this up on deploy. Edit it while it's still scheduled and the next deploy syncs the change; move sendAt and it reschedules. Once sent, it's retired — redeploys no-op.

anywhere-you-run-typescript.ts
await hs.campaigns.send({
  list: "product-updates",
  template: "marketing/product-update",
  props: { headline: "Saved views are here" },
  sendAt: "2026-07-15T16:00:00Z",
  idempotencyKey: "launch-2026-07",
});

The same thing at runtime — from the SDK, hogsend campaigns send in the CLI, or a plain POST to /v1/campaigns. Agents can drive this too.

Delivery

Scheduled, guarded, deduplicated

Campaign sends go down the same path as journey email: render, preference check, first-party tracking, then the provider wire.

Punctual, with a backstop

sendAt becomes a scheduled run in the workflow engine. A reaper cron sweeps every five minutes and promotes anything due that didn't fire.

No duplicate sends

Every recipient send carries an idempotency key. If a crash or retry re-runs the campaign, already-dispatched emails short-circuit and only the unsent tail goes out.

Preferences enforced twice

Unsubscribed and suppressed contacts are excluded when the audience resolves, and every individual send re-checks before the provider is called.

Never a surprise blast

A committed campaign whose sendAt is already stale on first deploy is marked expired, not sent.

Cancelable until it's done

Cancel a scheduled, queued, or in-flight campaign. Mid-send, delivery stops at the next chunk of 100 — dispatched email can't be recalled; the rest is spared.

Tracked like everything else

Opens and clicks are first-party: rewritten links and the pixel land in your database and PostHog, identical to journey sends, whatever provider is on the wire.

Audience

A list or a bucket, resolved at send time

{ list: "product-updates" } reaches every subscribed member — opt-in lists reach explicit subscribers, opt-out lists reach everyone who hasn't left. { bucket: "power-users" } reaches whoever is in the behavioral bucket at the moment the campaign runs. No CSV uploads, no exported segments going stale.

Studio

Authored in code, watched in Studio

The Campaigns view shows every campaign's status, audience, scheduled time, and live progress — sent, skipped, failed. One button cancels anything still in flight. Studio can't author campaigns; that stays in code, where it can be reviewed.

Coming from Resend Broadcasts

Broadcasts map one-to-one

An Audience becomes a list. A Broadcast becomes a campaign. A scheduled broadcast is sendAt, cancelable until it fires. Resend stays on as the send wire — same API key, same domains. Hogsend replaces the orchestration, not the delivery.

FAQ

Questions, answered

Short answers here; the docs go deeper.

Go deeper

A journey is triggered per user and has control flow — waits, branches, event checks. A campaign has none of that: one template, one audience, one instant. It fires once and is retired.

Send your first campaign

Scaffold an app with create-hogsend — the template ships with two example campaigns, disabled, ready to point at your list.

Free to self-host · One scaffold command · No per-contact billing

terminal
pnpm dlx create-hogsend@latest my-app