@hogsend/client
v0.22.0
Published
Typed HTTP client for the Hogsend data plane (contacts, events, emails, lists).
Readme
@hogsend/client
Typed HTTP client for the Hogsend data plane — contacts,
events, transactional emails, and lists. Thin wrapper over native fetch, no
heavy dependencies, ships compiled ESM + CJS + .d.ts.
Install
pnpm add @hogsend/clientFor fully type-checked emails.send, also install @hogsend/email (a
type-only optional peer) and augment its template registry in your app.
Without it, the emails.send shape degrades to { template: string; props? }.
Note: the shipped
.d.tsreferences@hogsend/emailby module name. If you do NOT install the optional peer, keepskipLibCheck: truein your tsconfig (the default for most app scaffolds) — otherwisetscemitsTS2307: Cannot find module '@hogsend/email'from this package's declarations. Installing the peer (even for types only) removes the caveat entirely. The runtime JS has no dependency on@hogsend/email.
Usage
import { Hogsend } from "@hogsend/client";
const hs = new Hogsend({
baseUrl: "https://api.example.com",
apiKey: process.env.HOGSEND_DATA_KEY!, // hsk_… key with the `ingest` scope
});
// Contacts ----------------------------------------------------------------
await hs.contacts.upsert({
email: "[email protected]",
userId: "u_1",
properties: { plan: "pro" },
lists: { newsletter: true },
}); // -> { id, created, linked }
const found = await hs.contacts.find({ email: "[email protected]" }); // Contact[]
await hs.contacts.delete({ userId: "u_1" }); // -> { deleted }
// Events ------------------------------------------------------------------
await hs.events.send({
userId: "u_1",
name: "signup",
eventProperties: { source: "landing" }, // → trigger.where / exitOn
contactProperties: { country: "GB" }, // → contact record
idempotencyKey: "evt_abc",
}); // -> { stored, exits: [{ journeyId, stateId, exited }] }
hs.events.track(/* … */); // alias of events.send
// Emails ------------------------------------------------------------------
await hs.emails.send({
to: "[email protected]",
template: "welcome",
props: { name: "Ada" },
}); // -> { emailSendId, status }
// Lists -------------------------------------------------------------------
await hs.lists.list(); // -> ListSummary[]
await hs.lists.subscribe({ list: "newsletter", email: "[email protected]" });
await hs.lists.unsubscribe({ list: "newsletter", userId: "u_1" });Identity
Every write takes an identity — at least one of email or userId
(your external id). Both may be supplied; the type union and a runtime guard
enforce that at least one is present.
Options
| Option | Type | Default | Notes |
| ----------- | -------------------------- | --------- | ----------------------------------------- |
| baseUrl | string | — | API base, e.g. https://api.example.com. |
| apiKey | string | — | Data-plane hsk_… key (ingest scope). |
| fetch | typeof fetch | global | Override for tests / custom agents. |
| timeoutMs | number | 30000 | Per-request timeout (aborts the request). |
| headers | Record<string, string> | {} | Extra headers on every request. |
Errors
All non-2xx responses (and transport failures) throw typed errors:
import { HogsendAPIError, RateLimitError } from "@hogsend/client";
try {
await hs.emails.send({ to: "[email protected]", template: "welcome", props: {} });
} catch (err) {
if (err instanceof RateLimitError) {
// 429 — back off for err.retryAfter seconds
} else if (err instanceof HogsendAPIError) {
// err.status (0 = transport failure), err.body (parsed JSON or raw text)
}
}HogsendAPIError—{ status, body }.status === 0means the request never reached the server (DNS/connect/timeout).RateLimitError extends HogsendAPIError—status === 429, withretryAfter(seconds, from theRetry-Afterheader) when present.
License
MIT
