@hogsend/client 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ import { TemplateRegistryMap } from '@hogsend/email';
2
+
3
+ /**
4
+ * A non-2xx response — or a transport-level failure — from the Hogsend data
5
+ * plane. `status` is the HTTP status code, or `0` when the request never
6
+ * reached the server (DNS/connect/timeout). `body` is the parsed JSON body when
7
+ * available, else the raw text, else `undefined`.
8
+ */
9
+ declare class HogsendAPIError extends Error {
10
+ readonly status: number;
11
+ readonly body: unknown;
12
+ constructor(message: string, status: number, body: unknown);
13
+ }
14
+ /**
15
+ * A `429 Too Many Requests` response. `retryAfter` is the parsed `Retry-After`
16
+ * header in seconds when present (the server sends it on 429 for `/v1/emails`).
17
+ */
18
+ declare class RateLimitError extends HogsendAPIError {
19
+ readonly retryAfter?: number;
20
+ constructor(message: string, body: unknown, retryAfter?: number);
21
+ }
22
+
23
+ /** Query params accepted by `get` — undefined values are dropped. */
24
+ type Query = Record<string, string | number | undefined>;
25
+ /** Per-request extras (currently just an idempotency header passthrough). */
26
+ interface RequestExtras {
27
+ /** Sent as the `Idempotency-Key` header when set. */
28
+ idempotencyKey?: string;
29
+ }
30
+ /** A minimal, self-contained data-plane HTTP client over native fetch. */
31
+ interface HttpClient {
32
+ get<T = unknown>(path: string, query?: Query): Promise<T>;
33
+ post<T = unknown>(path: string, body: unknown, extras?: RequestExtras): Promise<T>;
34
+ put<T = unknown>(path: string, body: unknown, extras?: RequestExtras): Promise<T>;
35
+ del<T = unknown>(path: string, body?: unknown): Promise<T>;
36
+ }
37
+
38
+ /**
39
+ * At least one resolvable identity key. Either `email` or `userId` (external id)
40
+ * must be present; both may be supplied. Enforced at the type level by the union
41
+ * and at runtime by `assertIdentity`.
42
+ */
43
+ type Identity = {
44
+ email: string;
45
+ userId?: string;
46
+ } | {
47
+ email?: string;
48
+ userId: string;
49
+ };
50
+ interface HogsendOptions {
51
+ /** Base URL of the Hogsend API, e.g. `https://api.example.com`. */
52
+ baseUrl: string;
53
+ /** A data-plane API key (`hsk_…`) with the `ingest` scope. */
54
+ apiKey: string;
55
+ /** Override the global `fetch` (tests, custom agents). Defaults to `fetch`. */
56
+ fetch?: typeof fetch;
57
+ /** Per-request timeout in milliseconds. Default `30_000`. */
58
+ timeoutMs?: number;
59
+ /** Extra headers merged onto every request (e.g. a tracing header). */
60
+ headers?: Record<string, string>;
61
+ }
62
+ /** The serialized contact shape returned by `/v1/contacts/find` (§2.5). */
63
+ interface Contact {
64
+ id: string;
65
+ externalId: string | null;
66
+ email: string | null;
67
+ properties: Record<string, unknown>;
68
+ firstSeenAt: string;
69
+ lastSeenAt: string;
70
+ createdAt: string;
71
+ updatedAt: string;
72
+ }
73
+ /** Result of a single exit-condition evaluation, returned by `/v1/events`. */
74
+ interface ExitResult {
75
+ journeyId: string;
76
+ stateId: string;
77
+ exited: boolean;
78
+ }
79
+ /** Result body of `POST /v1/events`. */
80
+ interface IngestResult {
81
+ stored: boolean;
82
+ exits: ExitResult[];
83
+ /**
84
+ * Present only when the event was ingested but the (non-atomic, post-ingest)
85
+ * list-membership write failed. The event itself is durably stored.
86
+ */
87
+ listsError?: string;
88
+ }
89
+ /** Result of `contacts.upsert`. */
90
+ interface UpsertContactResult {
91
+ id: string;
92
+ created: boolean;
93
+ linked: boolean;
94
+ }
95
+ /** Result of `contacts.delete`. */
96
+ interface DeleteContactResult {
97
+ deleted: boolean;
98
+ }
99
+ /** A list as returned by `GET /v1/lists`. */
100
+ interface ListSummary {
101
+ id: string;
102
+ name: string;
103
+ description?: string;
104
+ defaultOptIn: boolean;
105
+ }
106
+ /** Result of `emails.send`. */
107
+ interface SendEmailResult {
108
+ emailSendId: string;
109
+ status: string;
110
+ reason?: string;
111
+ }
112
+ /** Lifecycle status of a campaign (broadcast). */
113
+ type CampaignStatus = "queued" | "sending" | "sent" | "failed";
114
+ /** Whether a campaign targets a list or a bucket. */
115
+ type CampaignAudienceKind = "list" | "bucket";
116
+ /** Result of `campaigns.send` (the 202 enqueue ack from `POST /v1/campaigns`). */
117
+ interface SendCampaignResult {
118
+ campaignId: string;
119
+ status: CampaignStatus;
120
+ }
121
+ /** A campaign as returned by `GET /v1/campaigns/{id}`. */
122
+ interface Campaign {
123
+ id: string;
124
+ name: string;
125
+ status: CampaignStatus;
126
+ audienceKind: CampaignAudienceKind;
127
+ audienceId: string;
128
+ templateKey: string;
129
+ totalRecipients: number;
130
+ sentCount: number;
131
+ skippedCount: number;
132
+ failedCount: number;
133
+ startedAt: string | null;
134
+ completedAt: string | null;
135
+ createdAt: string;
136
+ }
137
+ type UpsertContactInput = Identity & {
138
+ properties?: Record<string, unknown>;
139
+ lists?: Record<string, boolean>;
140
+ };
141
+ type FindContactsInput = {
142
+ email: string;
143
+ } | {
144
+ userId: string;
145
+ };
146
+ type DeleteContactInput = Identity;
147
+ type SendEventInput = Identity & {
148
+ name: string;
149
+ eventProperties?: Record<string, unknown>;
150
+ contactProperties?: Record<string, unknown>;
151
+ lists?: Record<string, boolean>;
152
+ idempotencyKey?: string;
153
+ };
154
+ type SubscribeInput = Identity & {
155
+ list: string;
156
+ };
157
+ /** Result of `lists.subscribe` / `lists.unsubscribe`. */
158
+ interface SubscribeResult {
159
+ subscribed: boolean;
160
+ }
161
+ interface UnsubscribeResult {
162
+ unsubscribed: boolean;
163
+ }
164
+ /**
165
+ * `true` when `TemplateRegistryMap` carries no augmented keys (consumer has not
166
+ * declared their templates). `[keyof TemplateRegistryMap] extends [never]` is
167
+ * the distribution-safe "is `never`" check.
168
+ */
169
+ type IsEmptyRegistry = [keyof TemplateRegistryMap] extends [never] ? true : false;
170
+ /** Recipient + envelope fields shared by every `emails.send` call. */
171
+ type SendEmailEnvelope = {
172
+ to?: string;
173
+ userId?: string;
174
+ from?: string;
175
+ subject?: string;
176
+ replyTo?: string;
177
+ category?: string;
178
+ skipPreferenceCheck?: boolean;
179
+ idempotencyKey?: string;
180
+ };
181
+ /** One `{ template, props }` variant for a single known template key. */
182
+ type TypedTemplateVariant = {
183
+ [K in keyof TemplateRegistryMap]: SendEmailEnvelope & {
184
+ template: K;
185
+ props: TemplateRegistryMap[K];
186
+ };
187
+ }[keyof TemplateRegistryMap];
188
+ /** Fallback shape when `@hogsend/email` is absent/un-augmented. */
189
+ type UntypedTemplateVariant = SendEmailEnvelope & {
190
+ template: string;
191
+ props?: Record<string, unknown>;
192
+ };
193
+ /**
194
+ * Input to `emails.send`. When the consumer augments `TemplateRegistryMap`,
195
+ * `template`/`props` are fully type-checked per known key; otherwise it degrades
196
+ * to a permissive `{ template: string; props? }`.
197
+ */
198
+ type SendEmailInput = IsEmptyRegistry extends true ? UntypedTemplateVariant : TypedTemplateVariant;
199
+ /**
200
+ * Audience selector: exactly one of `list` or `bucket` (a list id or a bucket
201
+ * id). Modelled as a union so passing both is a type error, mirroring the
202
+ * server's "exactly one of list|bucket required" validation.
203
+ */
204
+ type CampaignAudience = {
205
+ list: string;
206
+ bucket?: never;
207
+ } | {
208
+ bucket: string;
209
+ list?: never;
210
+ };
211
+ /** Envelope fields shared by every `campaigns.send` call. */
212
+ type CampaignEnvelope = {
213
+ /** Human label for the campaign. Server defaults it when omitted. */
214
+ name?: string;
215
+ /** Override the default From address for this broadcast. */
216
+ from?: string;
217
+ /** Override the rendered subject for this broadcast. */
218
+ subject?: string;
219
+ };
220
+ /** One `{ template, props }` variant for a single known template key. */
221
+ type TypedCampaignTemplate = {
222
+ [K in keyof TemplateRegistryMap]: CampaignEnvelope & CampaignAudience & {
223
+ template: K;
224
+ props: TemplateRegistryMap[K];
225
+ };
226
+ }[keyof TemplateRegistryMap];
227
+ /** Fallback shape when `@hogsend/email` is absent/un-augmented. */
228
+ type UntypedCampaignTemplate = CampaignEnvelope & CampaignAudience & {
229
+ template: string;
230
+ props?: Record<string, unknown>;
231
+ };
232
+ /**
233
+ * Input to `campaigns.send`. Requires exactly one of `list` | `bucket` plus a
234
+ * `template`. When the consumer augments `TemplateRegistryMap`,
235
+ * `template`/`props` are fully type-checked per known key; otherwise it degrades
236
+ * to a permissive `{ template: string; props? }`.
237
+ */
238
+ type SendCampaignInput = IsEmptyRegistry extends true ? UntypedCampaignTemplate : TypedCampaignTemplate;
239
+
240
+ /** The `campaigns.*` resource bound to an {@link HttpClient}. */
241
+ declare class CampaignsResource {
242
+ private readonly http;
243
+ constructor(http: HttpClient);
244
+ /**
245
+ * Queue a broadcast: durably send one template to every subscribed member of
246
+ * a `list` (or every active member of a `bucket`). Exactly one of `list` /
247
+ * `bucket` must be set; `template`/`props` are type-checked against the
248
+ * augmented `TemplateRegistryMap` when `@hogsend/email` is installed, else
249
+ * degrade to `{ template: string; props? }`.
250
+ *
251
+ * Returns the 202 enqueue ack (`{ campaignId, status }`); the actual sends run
252
+ * asynchronously in the worker. Poll {@link CampaignsResource.get} for counts.
253
+ */
254
+ send(input: SendCampaignInput): Promise<SendCampaignResult>;
255
+ /** Fetch a campaign's current status + send counts. */
256
+ get(id: string): Promise<Campaign>;
257
+ }
258
+
259
+ /** The `contacts.*` resource bound to an {@link HttpClient}. */
260
+ declare class ContactsResource {
261
+ private readonly http;
262
+ constructor(http: HttpClient);
263
+ /**
264
+ * Upsert a contact by identity. Resolves/merges server-side and optionally
265
+ * applies list membership. Returns `{ id, created, linked }`.
266
+ */
267
+ upsert(input: UpsertContactInput): Promise<UpsertContactResult>;
268
+ /** Find non-deleted contacts by `email` or `userId`. */
269
+ find(input: FindContactsInput): Promise<Contact[]>;
270
+ /** Soft-delete a contact by identity. */
271
+ delete(input: DeleteContactInput): Promise<DeleteContactResult>;
272
+ }
273
+
274
+ /** The `emails.*` resource bound to an {@link HttpClient}. */
275
+ declare class EmailsResource {
276
+ private readonly http;
277
+ constructor(http: HttpClient);
278
+ /**
279
+ * Send a transactional email by template. Recipient is `to` (raw address) or
280
+ * `userId` (resolved server-side). `template`/`props` are type-checked against
281
+ * the augmented `TemplateRegistryMap` when `@hogsend/email` is installed,
282
+ * else degrade to `{ template: string; props? }`.
283
+ */
284
+ send(input: SendEmailInput): Promise<SendEmailResult>;
285
+ }
286
+
287
+ /** The `events.*` resource bound to an {@link HttpClient}. */
288
+ declare class EventsResource {
289
+ private readonly http;
290
+ constructor(http: HttpClient);
291
+ /**
292
+ * Send an event through the ingestion pipeline. The two property bags are
293
+ * kept distinct: `eventProperties` feed `trigger.where`/`exitOn`,
294
+ * `contactProperties` merge onto the contact. Optionally apply list
295
+ * membership. Returns the ingest result (stored + exit evaluations).
296
+ *
297
+ * `idempotencyKey` is sent both as the `Idempotency-Key` header (which wins
298
+ * server-side) and in the body, matching `POST /v1/events`.
299
+ */
300
+ send(input: SendEventInput): Promise<IngestResult>;
301
+ /** Alias of {@link EventsResource.send}. */
302
+ track(input: SendEventInput): Promise<IngestResult>;
303
+ }
304
+
305
+ /** The `lists.*` resource bound to an {@link HttpClient}. */
306
+ declare class ListsResource {
307
+ private readonly http;
308
+ constructor(http: HttpClient);
309
+ /** List all code-defined lists. */
310
+ list(): Promise<ListSummary[]>;
311
+ /** Subscribe an identity to a list. */
312
+ subscribe(input: SubscribeInput): Promise<SubscribeResult>;
313
+ /** Unsubscribe an identity from a list. */
314
+ unsubscribe(input: SubscribeInput): Promise<UnsubscribeResult>;
315
+ }
316
+
317
+ /**
318
+ * Typed HTTP client for the Hogsend data plane.
319
+ *
320
+ * ```ts
321
+ * const hs = new Hogsend({ baseUrl: "https://api.example.com", apiKey: "hsk_…" });
322
+ * await hs.contacts.upsert({ email: "a@b.com", properties: { plan: "pro" } });
323
+ * await hs.events.send({ userId: "u_1", name: "signup" });
324
+ * ```
325
+ */
326
+ declare class Hogsend {
327
+ readonly contacts: ContactsResource;
328
+ readonly events: EventsResource;
329
+ readonly emails: EmailsResource;
330
+ readonly lists: ListsResource;
331
+ readonly campaigns: CampaignsResource;
332
+ constructor(opts: HogsendOptions);
333
+ }
334
+
335
+ export { type Campaign, type CampaignAudienceKind, type CampaignStatus, type Contact, type DeleteContactInput, type DeleteContactResult, type ExitResult, type FindContactsInput, Hogsend, HogsendAPIError, type HogsendOptions, type Identity, type IngestResult, type ListSummary, RateLimitError, type SendCampaignInput, type SendCampaignResult, type SendEmailInput, type SendEmailResult, type SendEventInput, type SubscribeInput, type SubscribeResult, type UnsubscribeResult, type UpsertContactInput, type UpsertContactResult };
@@ -0,0 +1,335 @@
1
+ import { TemplateRegistryMap } from '@hogsend/email';
2
+
3
+ /**
4
+ * A non-2xx response — or a transport-level failure — from the Hogsend data
5
+ * plane. `status` is the HTTP status code, or `0` when the request never
6
+ * reached the server (DNS/connect/timeout). `body` is the parsed JSON body when
7
+ * available, else the raw text, else `undefined`.
8
+ */
9
+ declare class HogsendAPIError extends Error {
10
+ readonly status: number;
11
+ readonly body: unknown;
12
+ constructor(message: string, status: number, body: unknown);
13
+ }
14
+ /**
15
+ * A `429 Too Many Requests` response. `retryAfter` is the parsed `Retry-After`
16
+ * header in seconds when present (the server sends it on 429 for `/v1/emails`).
17
+ */
18
+ declare class RateLimitError extends HogsendAPIError {
19
+ readonly retryAfter?: number;
20
+ constructor(message: string, body: unknown, retryAfter?: number);
21
+ }
22
+
23
+ /** Query params accepted by `get` — undefined values are dropped. */
24
+ type Query = Record<string, string | number | undefined>;
25
+ /** Per-request extras (currently just an idempotency header passthrough). */
26
+ interface RequestExtras {
27
+ /** Sent as the `Idempotency-Key` header when set. */
28
+ idempotencyKey?: string;
29
+ }
30
+ /** A minimal, self-contained data-plane HTTP client over native fetch. */
31
+ interface HttpClient {
32
+ get<T = unknown>(path: string, query?: Query): Promise<T>;
33
+ post<T = unknown>(path: string, body: unknown, extras?: RequestExtras): Promise<T>;
34
+ put<T = unknown>(path: string, body: unknown, extras?: RequestExtras): Promise<T>;
35
+ del<T = unknown>(path: string, body?: unknown): Promise<T>;
36
+ }
37
+
38
+ /**
39
+ * At least one resolvable identity key. Either `email` or `userId` (external id)
40
+ * must be present; both may be supplied. Enforced at the type level by the union
41
+ * and at runtime by `assertIdentity`.
42
+ */
43
+ type Identity = {
44
+ email: string;
45
+ userId?: string;
46
+ } | {
47
+ email?: string;
48
+ userId: string;
49
+ };
50
+ interface HogsendOptions {
51
+ /** Base URL of the Hogsend API, e.g. `https://api.example.com`. */
52
+ baseUrl: string;
53
+ /** A data-plane API key (`hsk_…`) with the `ingest` scope. */
54
+ apiKey: string;
55
+ /** Override the global `fetch` (tests, custom agents). Defaults to `fetch`. */
56
+ fetch?: typeof fetch;
57
+ /** Per-request timeout in milliseconds. Default `30_000`. */
58
+ timeoutMs?: number;
59
+ /** Extra headers merged onto every request (e.g. a tracing header). */
60
+ headers?: Record<string, string>;
61
+ }
62
+ /** The serialized contact shape returned by `/v1/contacts/find` (§2.5). */
63
+ interface Contact {
64
+ id: string;
65
+ externalId: string | null;
66
+ email: string | null;
67
+ properties: Record<string, unknown>;
68
+ firstSeenAt: string;
69
+ lastSeenAt: string;
70
+ createdAt: string;
71
+ updatedAt: string;
72
+ }
73
+ /** Result of a single exit-condition evaluation, returned by `/v1/events`. */
74
+ interface ExitResult {
75
+ journeyId: string;
76
+ stateId: string;
77
+ exited: boolean;
78
+ }
79
+ /** Result body of `POST /v1/events`. */
80
+ interface IngestResult {
81
+ stored: boolean;
82
+ exits: ExitResult[];
83
+ /**
84
+ * Present only when the event was ingested but the (non-atomic, post-ingest)
85
+ * list-membership write failed. The event itself is durably stored.
86
+ */
87
+ listsError?: string;
88
+ }
89
+ /** Result of `contacts.upsert`. */
90
+ interface UpsertContactResult {
91
+ id: string;
92
+ created: boolean;
93
+ linked: boolean;
94
+ }
95
+ /** Result of `contacts.delete`. */
96
+ interface DeleteContactResult {
97
+ deleted: boolean;
98
+ }
99
+ /** A list as returned by `GET /v1/lists`. */
100
+ interface ListSummary {
101
+ id: string;
102
+ name: string;
103
+ description?: string;
104
+ defaultOptIn: boolean;
105
+ }
106
+ /** Result of `emails.send`. */
107
+ interface SendEmailResult {
108
+ emailSendId: string;
109
+ status: string;
110
+ reason?: string;
111
+ }
112
+ /** Lifecycle status of a campaign (broadcast). */
113
+ type CampaignStatus = "queued" | "sending" | "sent" | "failed";
114
+ /** Whether a campaign targets a list or a bucket. */
115
+ type CampaignAudienceKind = "list" | "bucket";
116
+ /** Result of `campaigns.send` (the 202 enqueue ack from `POST /v1/campaigns`). */
117
+ interface SendCampaignResult {
118
+ campaignId: string;
119
+ status: CampaignStatus;
120
+ }
121
+ /** A campaign as returned by `GET /v1/campaigns/{id}`. */
122
+ interface Campaign {
123
+ id: string;
124
+ name: string;
125
+ status: CampaignStatus;
126
+ audienceKind: CampaignAudienceKind;
127
+ audienceId: string;
128
+ templateKey: string;
129
+ totalRecipients: number;
130
+ sentCount: number;
131
+ skippedCount: number;
132
+ failedCount: number;
133
+ startedAt: string | null;
134
+ completedAt: string | null;
135
+ createdAt: string;
136
+ }
137
+ type UpsertContactInput = Identity & {
138
+ properties?: Record<string, unknown>;
139
+ lists?: Record<string, boolean>;
140
+ };
141
+ type FindContactsInput = {
142
+ email: string;
143
+ } | {
144
+ userId: string;
145
+ };
146
+ type DeleteContactInput = Identity;
147
+ type SendEventInput = Identity & {
148
+ name: string;
149
+ eventProperties?: Record<string, unknown>;
150
+ contactProperties?: Record<string, unknown>;
151
+ lists?: Record<string, boolean>;
152
+ idempotencyKey?: string;
153
+ };
154
+ type SubscribeInput = Identity & {
155
+ list: string;
156
+ };
157
+ /** Result of `lists.subscribe` / `lists.unsubscribe`. */
158
+ interface SubscribeResult {
159
+ subscribed: boolean;
160
+ }
161
+ interface UnsubscribeResult {
162
+ unsubscribed: boolean;
163
+ }
164
+ /**
165
+ * `true` when `TemplateRegistryMap` carries no augmented keys (consumer has not
166
+ * declared their templates). `[keyof TemplateRegistryMap] extends [never]` is
167
+ * the distribution-safe "is `never`" check.
168
+ */
169
+ type IsEmptyRegistry = [keyof TemplateRegistryMap] extends [never] ? true : false;
170
+ /** Recipient + envelope fields shared by every `emails.send` call. */
171
+ type SendEmailEnvelope = {
172
+ to?: string;
173
+ userId?: string;
174
+ from?: string;
175
+ subject?: string;
176
+ replyTo?: string;
177
+ category?: string;
178
+ skipPreferenceCheck?: boolean;
179
+ idempotencyKey?: string;
180
+ };
181
+ /** One `{ template, props }` variant for a single known template key. */
182
+ type TypedTemplateVariant = {
183
+ [K in keyof TemplateRegistryMap]: SendEmailEnvelope & {
184
+ template: K;
185
+ props: TemplateRegistryMap[K];
186
+ };
187
+ }[keyof TemplateRegistryMap];
188
+ /** Fallback shape when `@hogsend/email` is absent/un-augmented. */
189
+ type UntypedTemplateVariant = SendEmailEnvelope & {
190
+ template: string;
191
+ props?: Record<string, unknown>;
192
+ };
193
+ /**
194
+ * Input to `emails.send`. When the consumer augments `TemplateRegistryMap`,
195
+ * `template`/`props` are fully type-checked per known key; otherwise it degrades
196
+ * to a permissive `{ template: string; props? }`.
197
+ */
198
+ type SendEmailInput = IsEmptyRegistry extends true ? UntypedTemplateVariant : TypedTemplateVariant;
199
+ /**
200
+ * Audience selector: exactly one of `list` or `bucket` (a list id or a bucket
201
+ * id). Modelled as a union so passing both is a type error, mirroring the
202
+ * server's "exactly one of list|bucket required" validation.
203
+ */
204
+ type CampaignAudience = {
205
+ list: string;
206
+ bucket?: never;
207
+ } | {
208
+ bucket: string;
209
+ list?: never;
210
+ };
211
+ /** Envelope fields shared by every `campaigns.send` call. */
212
+ type CampaignEnvelope = {
213
+ /** Human label for the campaign. Server defaults it when omitted. */
214
+ name?: string;
215
+ /** Override the default From address for this broadcast. */
216
+ from?: string;
217
+ /** Override the rendered subject for this broadcast. */
218
+ subject?: string;
219
+ };
220
+ /** One `{ template, props }` variant for a single known template key. */
221
+ type TypedCampaignTemplate = {
222
+ [K in keyof TemplateRegistryMap]: CampaignEnvelope & CampaignAudience & {
223
+ template: K;
224
+ props: TemplateRegistryMap[K];
225
+ };
226
+ }[keyof TemplateRegistryMap];
227
+ /** Fallback shape when `@hogsend/email` is absent/un-augmented. */
228
+ type UntypedCampaignTemplate = CampaignEnvelope & CampaignAudience & {
229
+ template: string;
230
+ props?: Record<string, unknown>;
231
+ };
232
+ /**
233
+ * Input to `campaigns.send`. Requires exactly one of `list` | `bucket` plus a
234
+ * `template`. When the consumer augments `TemplateRegistryMap`,
235
+ * `template`/`props` are fully type-checked per known key; otherwise it degrades
236
+ * to a permissive `{ template: string; props? }`.
237
+ */
238
+ type SendCampaignInput = IsEmptyRegistry extends true ? UntypedCampaignTemplate : TypedCampaignTemplate;
239
+
240
+ /** The `campaigns.*` resource bound to an {@link HttpClient}. */
241
+ declare class CampaignsResource {
242
+ private readonly http;
243
+ constructor(http: HttpClient);
244
+ /**
245
+ * Queue a broadcast: durably send one template to every subscribed member of
246
+ * a `list` (or every active member of a `bucket`). Exactly one of `list` /
247
+ * `bucket` must be set; `template`/`props` are type-checked against the
248
+ * augmented `TemplateRegistryMap` when `@hogsend/email` is installed, else
249
+ * degrade to `{ template: string; props? }`.
250
+ *
251
+ * Returns the 202 enqueue ack (`{ campaignId, status }`); the actual sends run
252
+ * asynchronously in the worker. Poll {@link CampaignsResource.get} for counts.
253
+ */
254
+ send(input: SendCampaignInput): Promise<SendCampaignResult>;
255
+ /** Fetch a campaign's current status + send counts. */
256
+ get(id: string): Promise<Campaign>;
257
+ }
258
+
259
+ /** The `contacts.*` resource bound to an {@link HttpClient}. */
260
+ declare class ContactsResource {
261
+ private readonly http;
262
+ constructor(http: HttpClient);
263
+ /**
264
+ * Upsert a contact by identity. Resolves/merges server-side and optionally
265
+ * applies list membership. Returns `{ id, created, linked }`.
266
+ */
267
+ upsert(input: UpsertContactInput): Promise<UpsertContactResult>;
268
+ /** Find non-deleted contacts by `email` or `userId`. */
269
+ find(input: FindContactsInput): Promise<Contact[]>;
270
+ /** Soft-delete a contact by identity. */
271
+ delete(input: DeleteContactInput): Promise<DeleteContactResult>;
272
+ }
273
+
274
+ /** The `emails.*` resource bound to an {@link HttpClient}. */
275
+ declare class EmailsResource {
276
+ private readonly http;
277
+ constructor(http: HttpClient);
278
+ /**
279
+ * Send a transactional email by template. Recipient is `to` (raw address) or
280
+ * `userId` (resolved server-side). `template`/`props` are type-checked against
281
+ * the augmented `TemplateRegistryMap` when `@hogsend/email` is installed,
282
+ * else degrade to `{ template: string; props? }`.
283
+ */
284
+ send(input: SendEmailInput): Promise<SendEmailResult>;
285
+ }
286
+
287
+ /** The `events.*` resource bound to an {@link HttpClient}. */
288
+ declare class EventsResource {
289
+ private readonly http;
290
+ constructor(http: HttpClient);
291
+ /**
292
+ * Send an event through the ingestion pipeline. The two property bags are
293
+ * kept distinct: `eventProperties` feed `trigger.where`/`exitOn`,
294
+ * `contactProperties` merge onto the contact. Optionally apply list
295
+ * membership. Returns the ingest result (stored + exit evaluations).
296
+ *
297
+ * `idempotencyKey` is sent both as the `Idempotency-Key` header (which wins
298
+ * server-side) and in the body, matching `POST /v1/events`.
299
+ */
300
+ send(input: SendEventInput): Promise<IngestResult>;
301
+ /** Alias of {@link EventsResource.send}. */
302
+ track(input: SendEventInput): Promise<IngestResult>;
303
+ }
304
+
305
+ /** The `lists.*` resource bound to an {@link HttpClient}. */
306
+ declare class ListsResource {
307
+ private readonly http;
308
+ constructor(http: HttpClient);
309
+ /** List all code-defined lists. */
310
+ list(): Promise<ListSummary[]>;
311
+ /** Subscribe an identity to a list. */
312
+ subscribe(input: SubscribeInput): Promise<SubscribeResult>;
313
+ /** Unsubscribe an identity from a list. */
314
+ unsubscribe(input: SubscribeInput): Promise<UnsubscribeResult>;
315
+ }
316
+
317
+ /**
318
+ * Typed HTTP client for the Hogsend data plane.
319
+ *
320
+ * ```ts
321
+ * const hs = new Hogsend({ baseUrl: "https://api.example.com", apiKey: "hsk_…" });
322
+ * await hs.contacts.upsert({ email: "a@b.com", properties: { plan: "pro" } });
323
+ * await hs.events.send({ userId: "u_1", name: "signup" });
324
+ * ```
325
+ */
326
+ declare class Hogsend {
327
+ readonly contacts: ContactsResource;
328
+ readonly events: EventsResource;
329
+ readonly emails: EmailsResource;
330
+ readonly lists: ListsResource;
331
+ readonly campaigns: CampaignsResource;
332
+ constructor(opts: HogsendOptions);
333
+ }
334
+
335
+ export { type Campaign, type CampaignAudienceKind, type CampaignStatus, type Contact, type DeleteContactInput, type DeleteContactResult, type ExitResult, type FindContactsInput, Hogsend, HogsendAPIError, type HogsendOptions, type Identity, type IngestResult, type ListSummary, RateLimitError, type SendCampaignInput, type SendCampaignResult, type SendEmailInput, type SendEmailResult, type SendEventInput, type SubscribeInput, type SubscribeResult, type UnsubscribeResult, type UpsertContactInput, type UpsertContactResult };