bitelio 0.1.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,328 @@
1
+ interface BitelioOptions {
2
+ /**
3
+ * Your project's public key (`pk_…`), needed ONLY by `events.track`.
4
+ *
5
+ * That endpoint authenticates with the public key rather than the secret one — it is the same
6
+ * endpoint browsers and mobile apps post to, and it has no secret-key equivalent that takes an
7
+ * email address. Nothing is lost by supplying it here: a public key is not a secret, it ships in
8
+ * every page of your site.
9
+ */
10
+ publicKey?: string;
11
+ /** Defaults to `https://api.bitelio.com`. Point it at your own deployment if self-hosted. */
12
+ baseUrl?: string;
13
+ /** Per attempt, not for the whole call. Default 30s. */
14
+ timeoutMs?: number;
15
+ /** Retries AFTER the first attempt, on 429 and 5xx only. Default 2. */
16
+ maxRetries?: number;
17
+ /** Swap in a stub in tests, or a proxying fetch in production. */
18
+ fetch?: typeof globalThis.fetch;
19
+ }
20
+ interface RequestOptions {
21
+ query?: Record<string, string | number | boolean | undefined>;
22
+ body?: unknown;
23
+ idempotencyKey?: string;
24
+ /** Overrides the bearer token for this call. Used by `events.track`, which needs the public key. */
25
+ authToken?: string;
26
+ }
27
+ /**
28
+ * The transport. Everything a resource does goes through `request`, so the retry policy, the
29
+ * timeout and the error shape are decided in exactly one place.
30
+ */
31
+ declare class HttpClient {
32
+ private readonly apiKey;
33
+ readonly publicKey: string | undefined;
34
+ private readonly baseUrl;
35
+ private readonly timeoutMs;
36
+ private readonly maxRetries;
37
+ private readonly fetchImpl;
38
+ constructor(apiKey: string, options?: BitelioOptions);
39
+ request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
40
+ /** A key per call, so the retry above is safe by default rather than by remembering. */
41
+ static newIdempotencyKey(): string;
42
+ }
43
+
44
+ /** Whether a send actually went out. A test key records everything and delivers nothing. */
45
+ type EmailMode = 'live' | 'test';
46
+ type EmailStatus = 'PENDING' | 'SENDING' | 'HELD' | 'SENT' | 'DELIVERED' | 'RECEIVED' | 'OPENED' | 'CLICKED' | 'BOUNCED' | 'COMPLAINED' | 'FAILED';
47
+ /** A send, as it appears in a listing. The rendered body is on `emails.get` only. */
48
+ interface Email {
49
+ id: string;
50
+ to: string;
51
+ subject: string;
52
+ from: string;
53
+ fromName: string | null;
54
+ status: EmailStatus;
55
+ testMode: boolean;
56
+ /** The provider's id. Prefixed `test-` for a send that never left. */
57
+ messageId: string | null;
58
+ error: string | null;
59
+ opens: number;
60
+ clicks: number;
61
+ createdAt: string;
62
+ sentAt: string | null;
63
+ deliveredAt: string | null;
64
+ openedAt: string | null;
65
+ clickedAt: string | null;
66
+ bouncedAt: string | null;
67
+ complainedAt: string | null;
68
+ }
69
+ /** One send, with the HTML that actually went out. */
70
+ interface EmailDetail extends Email {
71
+ body: string;
72
+ replyTo: string | null;
73
+ toName: string | null;
74
+ headers: Record<string, string> | null;
75
+ sourceType: string;
76
+ }
77
+ interface EmailListParams {
78
+ limit?: number;
79
+ /** The previous page's `nextCursor`, verbatim. Opaque. */
80
+ cursor?: string;
81
+ /** `live` unless you say otherwise — a test send is noise a day later. */
82
+ mode?: 'live' | 'test' | 'all';
83
+ status?: EmailStatus;
84
+ /** Recipient address. Empty page if they are not a contact of your project. */
85
+ to?: string;
86
+ since?: Date | string;
87
+ until?: Date | string;
88
+ }
89
+ interface Page<T> {
90
+ data: T[];
91
+ /** `null` on the last page. */
92
+ nextCursor: string | null;
93
+ }
94
+ type Recipient = string | {
95
+ name?: string;
96
+ email: string;
97
+ };
98
+ interface SendParams {
99
+ to: Recipient | Recipient[];
100
+ subject?: string;
101
+ body?: string;
102
+ /** A template id, instead of `subject` + `body`. */
103
+ template?: string;
104
+ from?: string | {
105
+ name?: string;
106
+ email: string;
107
+ };
108
+ name?: string;
109
+ reply?: string;
110
+ headers?: Record<string, string>;
111
+ data?: Record<string, unknown>;
112
+ subscribed?: boolean;
113
+ attachments?: {
114
+ filename: string;
115
+ content: string;
116
+ contentType: string;
117
+ }[];
118
+ /**
119
+ * Overrides the key generated for you. Supply your own when the natural unit of work is not one
120
+ * call — the same order confirmation retried across two processes, say.
121
+ */
122
+ idempotencyKey?: string;
123
+ }
124
+ interface SendResult {
125
+ emails: {
126
+ contact: {
127
+ id: string;
128
+ email: string;
129
+ };
130
+ email: string;
131
+ }[];
132
+ timestamp: string;
133
+ }
134
+ interface Contact {
135
+ id: string;
136
+ email: string;
137
+ subscribed: boolean;
138
+ data: Record<string, unknown> | null;
139
+ createdAt: string;
140
+ updatedAt: string;
141
+ }
142
+ interface TrackParams {
143
+ event: string;
144
+ email: string;
145
+ data?: Record<string, unknown>;
146
+ subscribed?: boolean;
147
+ }
148
+
149
+ interface CreateContactParams {
150
+ email: string;
151
+ subscribed?: boolean;
152
+ data?: Record<string, unknown>;
153
+ }
154
+ interface UpdateContactParams {
155
+ email?: string;
156
+ subscribed?: boolean;
157
+ data?: Record<string, unknown>;
158
+ }
159
+ declare class Contacts {
160
+ private readonly http;
161
+ constructor(http: HttpClient);
162
+ create(params: CreateContactParams): Promise<Contact>;
163
+ get(id: string): Promise<Contact>;
164
+ update(id: string, params: UpdateContactParams): Promise<Contact>;
165
+ /** Irreversible, and it takes the contact's send history with it. */
166
+ delete(id: string): Promise<void>;
167
+ }
168
+
169
+ declare class Emails {
170
+ private readonly http;
171
+ constructor(http: HttpClient);
172
+ /**
173
+ * Sends one transactional email, or one per recipient when `to` is an array.
174
+ *
175
+ * An idempotency key is generated for you unless you supply one. That is not a nicety: this
176
+ * client retries 429s and 5xx responses, and a retried send without a key is a duplicate in
177
+ * somebody's inbox.
178
+ */
179
+ send(params: SendParams): Promise<SendResult>;
180
+ /**
181
+ * One page of your send history, newest first.
182
+ *
183
+ * Shows live sends only unless you pass `mode`. Page by handing `nextCursor` straight back —
184
+ * it is opaque, and `null` means you have reached the end.
185
+ */
186
+ list(params?: EmailListParams): Promise<Page<Email>>;
187
+ /**
188
+ * One email, including the HTML that actually went out — after variables were substituted,
189
+ * blocks resolved and your brand applied. Most "the email looks wrong" reports end here.
190
+ */
191
+ get(id: string): Promise<EmailDetail>;
192
+ /**
193
+ * Every send matching the filter, one page at a time.
194
+ *
195
+ * An async iterator rather than an array: a project's history does not fit in memory, and the
196
+ * shape of this method is what stops somebody discovering that in production.
197
+ */
198
+ iterate(params?: Omit<EmailListParams, 'cursor'>): AsyncGenerator<Email>;
199
+ }
200
+
201
+ interface TrackResult {
202
+ contact: string;
203
+ event: string;
204
+ timestamp: string;
205
+ }
206
+ declare class Events {
207
+ private readonly http;
208
+ constructor(http: HttpClient);
209
+ /**
210
+ * Records an event against a contact, creating them if they are new. This is what triggers a
211
+ * workflow, so it is the call a SaaS makes from its own lifecycle code.
212
+ *
213
+ * Values in `data` are saved onto the contact and are available to every later message. To pass
214
+ * something for this event only, wrap it: `{orderId: {value: '123', persistent: false}}`.
215
+ *
216
+ * Authenticates with your PUBLIC key, which is why the client has to be given one. This endpoint
217
+ * is the same one browsers post to, and today it has no secret-key equivalent that identifies a
218
+ * contact by email address — `POST /events/track` does exist for secret keys, but it requires a
219
+ * contact id you would have to go and look up first. Supplying the public key is not a
220
+ * concession: it ships in every page of your own site already.
221
+ */
222
+ track(params: TrackParams): Promise<TrackResult>;
223
+ }
224
+
225
+ /**
226
+ * Verifying a webhook Bitelio sent you.
227
+ *
228
+ * The signature is HMAC-SHA256 over `{timestamp}.{rawBody}`, sent as `Bitelio-Signature` beside
229
+ * `Bitelio-Timestamp`. The timestamp is inside the signed string so a replayed old request can be
230
+ * rejected — the same scheme Stripe uses, which is deliberate: it is the one developers already
231
+ * know how to verify by hand.
232
+ */
233
+ interface VerifyParams {
234
+ /**
235
+ * The RAW request body, as a string or Buffer — NOT a parsed object.
236
+ *
237
+ * This is the mistake everyone makes. `JSON.parse` then `JSON.stringify` reorders keys and
238
+ * changes whitespace, and the signature covers bytes. In Express, reach for
239
+ * `express.raw({type: 'application/json'})` on the webhook route.
240
+ */
241
+ payload: string | Buffer;
242
+ /** The `Bitelio-Signature` header, verbatim. */
243
+ signature: string;
244
+ /** The `Bitelio-Timestamp` header, verbatim. */
245
+ timestamp: string | number;
246
+ /** Your endpoint's signing secret. */
247
+ secret: string;
248
+ /**
249
+ * How old a request may be, in seconds. Default 300.
250
+ *
251
+ * This is what makes the timestamp worth signing: without a bound, a request captured once can
252
+ * be replayed for ever. Pass `Infinity` only if you have your own replay defence.
253
+ */
254
+ toleranceSeconds?: number;
255
+ }
256
+ /**
257
+ * True when the request really came from Bitelio and is recent enough.
258
+ *
259
+ * Accepts the request if ANY `v1=` part verifies. There is more than one during a secret
260
+ * rotation — the new secret and the still-live previous one — and a receiver mid-rollover that
261
+ * insisted on a single part would drop half its traffic.
262
+ */
263
+ declare function verify(params: VerifyParams): boolean;
264
+ /** `verify`, but it throws instead of returning false — handy at the top of a handler. */
265
+ declare function assertValid(params: VerifyParams): void;
266
+
267
+ type webhooks_VerifyParams = VerifyParams;
268
+ declare const webhooks_assertValid: typeof assertValid;
269
+ declare const webhooks_verify: typeof verify;
270
+ declare namespace webhooks {
271
+ export { type webhooks_VerifyParams as VerifyParams, webhooks_assertValid as assertValid, webhooks_verify as verify };
272
+ }
273
+
274
+ /**
275
+ * Every failure this client throws.
276
+ *
277
+ * `requestId` is the field worth knowing about: it comes back on every response, and quoting it in
278
+ * a support conversation is the difference between "I get a 500" and something answerable.
279
+ */
280
+ declare class BitelioError extends Error {
281
+ /** HTTP status. `0` when the request never reached the API — a DNS failure, a timeout. */
282
+ readonly status: number;
283
+ /** Machine-readable code, e.g. `VALIDATION_ERROR`. Absent on a transport failure. */
284
+ readonly code: string | undefined;
285
+ /** Quote this when reporting a problem. */
286
+ readonly requestId: string | undefined;
287
+ readonly details: Record<string, unknown> | undefined;
288
+ constructor(message: string, init: {
289
+ status: number;
290
+ code?: string;
291
+ requestId?: string;
292
+ details?: Record<string, unknown>;
293
+ });
294
+ /** True for the failures a retry could plausibly fix. */
295
+ get retryable(): boolean;
296
+ static fromResponse(status: number, requestId: string | undefined, body: unknown): BitelioError;
297
+ }
298
+
299
+ /**
300
+ * The Bitelio client.
301
+ *
302
+ * ```ts
303
+ * import {Bitelio} from 'bitelio';
304
+ *
305
+ * const bitelio = new Bitelio(process.env.BITELIO_API_KEY!);
306
+ *
307
+ * await bitelio.emails.send({
308
+ * from: 'onboarding@send.bitelio.com',
309
+ * to: 'you@yourcompany.com',
310
+ * subject: 'Hello',
311
+ * body: '<p>It works.</p>',
312
+ * });
313
+ * ```
314
+ *
315
+ * Covers sending, reading your send history, contacts and events — what you drive from code.
316
+ * Campaigns, segments, templates and workflows are designed in the dashboard and are deliberately
317
+ * not here.
318
+ */
319
+ declare class Bitelio {
320
+ readonly emails: Emails;
321
+ readonly contacts: Contacts;
322
+ readonly events: Events;
323
+ /** Verifying a webhook we sent you. Also exported standalone, for receivers with no client. */
324
+ static readonly webhooks: typeof webhooks;
325
+ constructor(apiKey: string, options?: BitelioOptions);
326
+ }
327
+
328
+ export { Bitelio, BitelioError, type BitelioOptions, type Contact, type CreateContactParams, type Email, type EmailDetail, type EmailListParams, type EmailMode, type EmailStatus, type Page, type Recipient, type SendParams, type SendResult, type TrackParams, type TrackResult, type UpdateContactParams, type VerifyParams, Bitelio as default, webhooks };
@@ -0,0 +1,328 @@
1
+ interface BitelioOptions {
2
+ /**
3
+ * Your project's public key (`pk_…`), needed ONLY by `events.track`.
4
+ *
5
+ * That endpoint authenticates with the public key rather than the secret one — it is the same
6
+ * endpoint browsers and mobile apps post to, and it has no secret-key equivalent that takes an
7
+ * email address. Nothing is lost by supplying it here: a public key is not a secret, it ships in
8
+ * every page of your site.
9
+ */
10
+ publicKey?: string;
11
+ /** Defaults to `https://api.bitelio.com`. Point it at your own deployment if self-hosted. */
12
+ baseUrl?: string;
13
+ /** Per attempt, not for the whole call. Default 30s. */
14
+ timeoutMs?: number;
15
+ /** Retries AFTER the first attempt, on 429 and 5xx only. Default 2. */
16
+ maxRetries?: number;
17
+ /** Swap in a stub in tests, or a proxying fetch in production. */
18
+ fetch?: typeof globalThis.fetch;
19
+ }
20
+ interface RequestOptions {
21
+ query?: Record<string, string | number | boolean | undefined>;
22
+ body?: unknown;
23
+ idempotencyKey?: string;
24
+ /** Overrides the bearer token for this call. Used by `events.track`, which needs the public key. */
25
+ authToken?: string;
26
+ }
27
+ /**
28
+ * The transport. Everything a resource does goes through `request`, so the retry policy, the
29
+ * timeout and the error shape are decided in exactly one place.
30
+ */
31
+ declare class HttpClient {
32
+ private readonly apiKey;
33
+ readonly publicKey: string | undefined;
34
+ private readonly baseUrl;
35
+ private readonly timeoutMs;
36
+ private readonly maxRetries;
37
+ private readonly fetchImpl;
38
+ constructor(apiKey: string, options?: BitelioOptions);
39
+ request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
40
+ /** A key per call, so the retry above is safe by default rather than by remembering. */
41
+ static newIdempotencyKey(): string;
42
+ }
43
+
44
+ /** Whether a send actually went out. A test key records everything and delivers nothing. */
45
+ type EmailMode = 'live' | 'test';
46
+ type EmailStatus = 'PENDING' | 'SENDING' | 'HELD' | 'SENT' | 'DELIVERED' | 'RECEIVED' | 'OPENED' | 'CLICKED' | 'BOUNCED' | 'COMPLAINED' | 'FAILED';
47
+ /** A send, as it appears in a listing. The rendered body is on `emails.get` only. */
48
+ interface Email {
49
+ id: string;
50
+ to: string;
51
+ subject: string;
52
+ from: string;
53
+ fromName: string | null;
54
+ status: EmailStatus;
55
+ testMode: boolean;
56
+ /** The provider's id. Prefixed `test-` for a send that never left. */
57
+ messageId: string | null;
58
+ error: string | null;
59
+ opens: number;
60
+ clicks: number;
61
+ createdAt: string;
62
+ sentAt: string | null;
63
+ deliveredAt: string | null;
64
+ openedAt: string | null;
65
+ clickedAt: string | null;
66
+ bouncedAt: string | null;
67
+ complainedAt: string | null;
68
+ }
69
+ /** One send, with the HTML that actually went out. */
70
+ interface EmailDetail extends Email {
71
+ body: string;
72
+ replyTo: string | null;
73
+ toName: string | null;
74
+ headers: Record<string, string> | null;
75
+ sourceType: string;
76
+ }
77
+ interface EmailListParams {
78
+ limit?: number;
79
+ /** The previous page's `nextCursor`, verbatim. Opaque. */
80
+ cursor?: string;
81
+ /** `live` unless you say otherwise — a test send is noise a day later. */
82
+ mode?: 'live' | 'test' | 'all';
83
+ status?: EmailStatus;
84
+ /** Recipient address. Empty page if they are not a contact of your project. */
85
+ to?: string;
86
+ since?: Date | string;
87
+ until?: Date | string;
88
+ }
89
+ interface Page<T> {
90
+ data: T[];
91
+ /** `null` on the last page. */
92
+ nextCursor: string | null;
93
+ }
94
+ type Recipient = string | {
95
+ name?: string;
96
+ email: string;
97
+ };
98
+ interface SendParams {
99
+ to: Recipient | Recipient[];
100
+ subject?: string;
101
+ body?: string;
102
+ /** A template id, instead of `subject` + `body`. */
103
+ template?: string;
104
+ from?: string | {
105
+ name?: string;
106
+ email: string;
107
+ };
108
+ name?: string;
109
+ reply?: string;
110
+ headers?: Record<string, string>;
111
+ data?: Record<string, unknown>;
112
+ subscribed?: boolean;
113
+ attachments?: {
114
+ filename: string;
115
+ content: string;
116
+ contentType: string;
117
+ }[];
118
+ /**
119
+ * Overrides the key generated for you. Supply your own when the natural unit of work is not one
120
+ * call — the same order confirmation retried across two processes, say.
121
+ */
122
+ idempotencyKey?: string;
123
+ }
124
+ interface SendResult {
125
+ emails: {
126
+ contact: {
127
+ id: string;
128
+ email: string;
129
+ };
130
+ email: string;
131
+ }[];
132
+ timestamp: string;
133
+ }
134
+ interface Contact {
135
+ id: string;
136
+ email: string;
137
+ subscribed: boolean;
138
+ data: Record<string, unknown> | null;
139
+ createdAt: string;
140
+ updatedAt: string;
141
+ }
142
+ interface TrackParams {
143
+ event: string;
144
+ email: string;
145
+ data?: Record<string, unknown>;
146
+ subscribed?: boolean;
147
+ }
148
+
149
+ interface CreateContactParams {
150
+ email: string;
151
+ subscribed?: boolean;
152
+ data?: Record<string, unknown>;
153
+ }
154
+ interface UpdateContactParams {
155
+ email?: string;
156
+ subscribed?: boolean;
157
+ data?: Record<string, unknown>;
158
+ }
159
+ declare class Contacts {
160
+ private readonly http;
161
+ constructor(http: HttpClient);
162
+ create(params: CreateContactParams): Promise<Contact>;
163
+ get(id: string): Promise<Contact>;
164
+ update(id: string, params: UpdateContactParams): Promise<Contact>;
165
+ /** Irreversible, and it takes the contact's send history with it. */
166
+ delete(id: string): Promise<void>;
167
+ }
168
+
169
+ declare class Emails {
170
+ private readonly http;
171
+ constructor(http: HttpClient);
172
+ /**
173
+ * Sends one transactional email, or one per recipient when `to` is an array.
174
+ *
175
+ * An idempotency key is generated for you unless you supply one. That is not a nicety: this
176
+ * client retries 429s and 5xx responses, and a retried send without a key is a duplicate in
177
+ * somebody's inbox.
178
+ */
179
+ send(params: SendParams): Promise<SendResult>;
180
+ /**
181
+ * One page of your send history, newest first.
182
+ *
183
+ * Shows live sends only unless you pass `mode`. Page by handing `nextCursor` straight back —
184
+ * it is opaque, and `null` means you have reached the end.
185
+ */
186
+ list(params?: EmailListParams): Promise<Page<Email>>;
187
+ /**
188
+ * One email, including the HTML that actually went out — after variables were substituted,
189
+ * blocks resolved and your brand applied. Most "the email looks wrong" reports end here.
190
+ */
191
+ get(id: string): Promise<EmailDetail>;
192
+ /**
193
+ * Every send matching the filter, one page at a time.
194
+ *
195
+ * An async iterator rather than an array: a project's history does not fit in memory, and the
196
+ * shape of this method is what stops somebody discovering that in production.
197
+ */
198
+ iterate(params?: Omit<EmailListParams, 'cursor'>): AsyncGenerator<Email>;
199
+ }
200
+
201
+ interface TrackResult {
202
+ contact: string;
203
+ event: string;
204
+ timestamp: string;
205
+ }
206
+ declare class Events {
207
+ private readonly http;
208
+ constructor(http: HttpClient);
209
+ /**
210
+ * Records an event against a contact, creating them if they are new. This is what triggers a
211
+ * workflow, so it is the call a SaaS makes from its own lifecycle code.
212
+ *
213
+ * Values in `data` are saved onto the contact and are available to every later message. To pass
214
+ * something for this event only, wrap it: `{orderId: {value: '123', persistent: false}}`.
215
+ *
216
+ * Authenticates with your PUBLIC key, which is why the client has to be given one. This endpoint
217
+ * is the same one browsers post to, and today it has no secret-key equivalent that identifies a
218
+ * contact by email address — `POST /events/track` does exist for secret keys, but it requires a
219
+ * contact id you would have to go and look up first. Supplying the public key is not a
220
+ * concession: it ships in every page of your own site already.
221
+ */
222
+ track(params: TrackParams): Promise<TrackResult>;
223
+ }
224
+
225
+ /**
226
+ * Verifying a webhook Bitelio sent you.
227
+ *
228
+ * The signature is HMAC-SHA256 over `{timestamp}.{rawBody}`, sent as `Bitelio-Signature` beside
229
+ * `Bitelio-Timestamp`. The timestamp is inside the signed string so a replayed old request can be
230
+ * rejected — the same scheme Stripe uses, which is deliberate: it is the one developers already
231
+ * know how to verify by hand.
232
+ */
233
+ interface VerifyParams {
234
+ /**
235
+ * The RAW request body, as a string or Buffer — NOT a parsed object.
236
+ *
237
+ * This is the mistake everyone makes. `JSON.parse` then `JSON.stringify` reorders keys and
238
+ * changes whitespace, and the signature covers bytes. In Express, reach for
239
+ * `express.raw({type: 'application/json'})` on the webhook route.
240
+ */
241
+ payload: string | Buffer;
242
+ /** The `Bitelio-Signature` header, verbatim. */
243
+ signature: string;
244
+ /** The `Bitelio-Timestamp` header, verbatim. */
245
+ timestamp: string | number;
246
+ /** Your endpoint's signing secret. */
247
+ secret: string;
248
+ /**
249
+ * How old a request may be, in seconds. Default 300.
250
+ *
251
+ * This is what makes the timestamp worth signing: without a bound, a request captured once can
252
+ * be replayed for ever. Pass `Infinity` only if you have your own replay defence.
253
+ */
254
+ toleranceSeconds?: number;
255
+ }
256
+ /**
257
+ * True when the request really came from Bitelio and is recent enough.
258
+ *
259
+ * Accepts the request if ANY `v1=` part verifies. There is more than one during a secret
260
+ * rotation — the new secret and the still-live previous one — and a receiver mid-rollover that
261
+ * insisted on a single part would drop half its traffic.
262
+ */
263
+ declare function verify(params: VerifyParams): boolean;
264
+ /** `verify`, but it throws instead of returning false — handy at the top of a handler. */
265
+ declare function assertValid(params: VerifyParams): void;
266
+
267
+ type webhooks_VerifyParams = VerifyParams;
268
+ declare const webhooks_assertValid: typeof assertValid;
269
+ declare const webhooks_verify: typeof verify;
270
+ declare namespace webhooks {
271
+ export { type webhooks_VerifyParams as VerifyParams, webhooks_assertValid as assertValid, webhooks_verify as verify };
272
+ }
273
+
274
+ /**
275
+ * Every failure this client throws.
276
+ *
277
+ * `requestId` is the field worth knowing about: it comes back on every response, and quoting it in
278
+ * a support conversation is the difference between "I get a 500" and something answerable.
279
+ */
280
+ declare class BitelioError extends Error {
281
+ /** HTTP status. `0` when the request never reached the API — a DNS failure, a timeout. */
282
+ readonly status: number;
283
+ /** Machine-readable code, e.g. `VALIDATION_ERROR`. Absent on a transport failure. */
284
+ readonly code: string | undefined;
285
+ /** Quote this when reporting a problem. */
286
+ readonly requestId: string | undefined;
287
+ readonly details: Record<string, unknown> | undefined;
288
+ constructor(message: string, init: {
289
+ status: number;
290
+ code?: string;
291
+ requestId?: string;
292
+ details?: Record<string, unknown>;
293
+ });
294
+ /** True for the failures a retry could plausibly fix. */
295
+ get retryable(): boolean;
296
+ static fromResponse(status: number, requestId: string | undefined, body: unknown): BitelioError;
297
+ }
298
+
299
+ /**
300
+ * The Bitelio client.
301
+ *
302
+ * ```ts
303
+ * import {Bitelio} from 'bitelio';
304
+ *
305
+ * const bitelio = new Bitelio(process.env.BITELIO_API_KEY!);
306
+ *
307
+ * await bitelio.emails.send({
308
+ * from: 'onboarding@send.bitelio.com',
309
+ * to: 'you@yourcompany.com',
310
+ * subject: 'Hello',
311
+ * body: '<p>It works.</p>',
312
+ * });
313
+ * ```
314
+ *
315
+ * Covers sending, reading your send history, contacts and events — what you drive from code.
316
+ * Campaigns, segments, templates and workflows are designed in the dashboard and are deliberately
317
+ * not here.
318
+ */
319
+ declare class Bitelio {
320
+ readonly emails: Emails;
321
+ readonly contacts: Contacts;
322
+ readonly events: Events;
323
+ /** Verifying a webhook we sent you. Also exported standalone, for receivers with no client. */
324
+ static readonly webhooks: typeof webhooks;
325
+ constructor(apiKey: string, options?: BitelioOptions);
326
+ }
327
+
328
+ export { Bitelio, BitelioError, type BitelioOptions, type Contact, type CreateContactParams, type Email, type EmailDetail, type EmailListParams, type EmailMode, type EmailStatus, type Page, type Recipient, type SendParams, type SendResult, type TrackParams, type TrackResult, type UpdateContactParams, type VerifyParams, Bitelio as default, webhooks };