@skrybe/sdk 1.0.0 → 2.0.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.
package/dist/index.d.ts CHANGED
@@ -1,52 +1,764 @@
1
- export interface SkrybeConfig {
1
+ type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
2
+ interface TransportOptions {
2
3
  apiKey: string;
3
- baseURL?: string;
4
+ baseUrl: string;
5
+ timeoutMs: number;
6
+ maxRetries: number;
7
+ fetch: FetchLike;
4
8
  }
5
- export interface SendEmailOptions {
9
+ type Fields = Record<string, string | number | undefined>;
10
+ interface RequestOptions {
11
+ /** Path relative to the install root, e.g. `api/lists/get-lists.php`. */
12
+ path: string;
13
+ /** Form fields. `api_key` is added automatically unless `anonymous`. */
14
+ body?: Fields;
15
+ /**
16
+ * `form` posts application/x-www-form-urlencoded, which is what every
17
+ * endpoint wants except api/emails/send-transactional.php, which reads
18
+ * php://input and needs a raw JSON body.
19
+ */
20
+ encoding?: 'form' | 'json';
21
+ method?: 'GET' | 'POST';
22
+ /** Skip the api_key: only api/stats/emails-sent.php is public. */
23
+ anonymous?: boolean;
24
+ /**
25
+ * Opt in to retrying this request. Off by default because most endpoints
26
+ * here create or send something, and none of them take an idempotency key:
27
+ * a 504 from a send that actually landed becomes a second campaign on
28
+ * retry. A dropped connection cannot be told apart from a response we never
29
+ * saw, so only reads set this.
30
+ */
31
+ retryable?: boolean;
32
+ }
33
+ declare class Transport {
34
+ private readonly opts;
35
+ constructor(opts: TransportOptions);
36
+ /**
37
+ * Perform a request and return the body as text.
38
+ *
39
+ * Legacy endpoints answer HTTP 200 with a prose error, so a 2xx here is not
40
+ * proof of success: recognised prose errors are thrown, and anything else is
41
+ * handed back for the caller to parse.
42
+ */
43
+ text(opts: RequestOptions): Promise<string>;
44
+ /** Perform a request against one of the endpoints that returns real JSON. */
45
+ json<T>(opts: RequestOptions): Promise<T>;
46
+ private send;
47
+ }
48
+
49
+ /** 0 off, 1 on, 2 anonymous. */
50
+ type Tracking = 0 | 1 | 2;
51
+ /** Opens, clicks, bounces and the rest, as campaign and autoresponder stats report them. */
52
+ interface EngagementStats {
53
+ recipients: number;
54
+ /** `rate` is a percentage of delivered (recipients less hard bounces). */
55
+ opens: {
56
+ total: number;
57
+ unique: number;
58
+ rate: number;
59
+ };
60
+ /** `rate` is a percentage of recipients. */
61
+ clicks: {
62
+ total: number;
63
+ unique: number;
64
+ rate: number;
65
+ };
66
+ bounces: {
67
+ hard: number;
68
+ soft: number;
69
+ };
70
+ complaints: number;
71
+ unsubscribes: number;
72
+ links: {
73
+ url: string;
74
+ clicks: number;
75
+ unique_clicks: number;
76
+ }[];
77
+ }
78
+
79
+ /**
80
+ * `drip` sends each email a set time after someone joins; `annually` and
81
+ * `on_date` send on a Date custom field, every year or once.
82
+ */
83
+ type AutoresponderType = 'drip' | 'annually' | 'on_date';
84
+ interface Autoresponder {
85
+ id: number;
86
+ name: string;
87
+ /** `unknown` for a type this SDK predates. */
88
+ type: AutoresponderType | 'unknown';
89
+ /** Encrypted, like the ids `lists.list()` returns. */
90
+ list_id: string;
91
+ /** The date field an `annually` or `on_date` autoresponder sends on. */
92
+ custom_field: string | null;
93
+ emails: number;
94
+ enabled_emails: number;
95
+ }
96
+ interface AutoresponderEmail {
97
+ id: number;
98
+ autoresponder_id: number;
99
+ subject: string;
100
+ from_name: string;
101
+ from_email: string;
102
+ /** As stored: "immediately", "+3 days", "-1 days", or "" for the date itself. */
103
+ when: string;
104
+ enabled: boolean;
105
+ recipients: number;
106
+ /** Unix seconds. */
107
+ created_at: number | null;
108
+ }
109
+ interface AutoresponderWithEmails extends Autoresponder {
110
+ email_list: AutoresponderEmail[];
111
+ }
112
+ interface AutoresponderEmailStats extends EngagementStats {
113
+ id: number;
114
+ autoresponder_id: number;
115
+ }
116
+ interface CreateAutoresponderParams {
117
+ name: string;
118
+ type: AutoresponderType;
119
+ /** The encrypted list id. A list can have one drip autoresponder. */
120
+ listId: string;
121
+ /** Required for `annually` and `on_date`: a Date custom field of the list, one autoresponder per field. */
122
+ customField?: string;
123
+ }
124
+ interface CreateAutoresponderEmailParams {
125
+ subject: string;
126
+ htmlText: string;
127
+ plainText?: string;
128
+ preheader?: string;
129
+ /**
130
+ * When it goes. For a drip, "immediately" (the default) or a delay such as
131
+ * "+3 days"; for annually and on_date, "on" (the default, the date itself)
132
+ * or an offset such as "+9 hours" or "-1 days". Units: minute, hour, day,
133
+ * week, month.
134
+ */
135
+ when?: string;
136
+ /** Each falls back to the brand's sender. */
137
+ fromName?: string;
138
+ fromEmail?: string;
139
+ replyTo?: string;
140
+ queryString?: string;
141
+ trackOpens?: Tracking;
142
+ trackClicks?: Tracking;
143
+ /** Emails are created off, as the dashboard creates them, unless this is true. */
144
+ enabled?: boolean;
145
+ }
146
+ declare class Autoresponders {
147
+ private readonly transport;
148
+ constructor(transport: Transport);
149
+ /** The brand's autoresponders, or one list's. */
150
+ list(opts?: {
151
+ listId?: string;
152
+ }): Promise<Autoresponder[]>;
153
+ /** One autoresponder with its emails. */
154
+ get(autoresponderId: number): Promise<AutoresponderWithEmails>;
155
+ emailStats(emailId: number): Promise<AutoresponderEmailStats>;
156
+ /** Creates an autoresponder with no emails; add them with `createEmail()`. Not retried. */
157
+ create(params: CreateAutoresponderParams): Promise<Autoresponder>;
158
+ /** Adds an email to an autoresponder. Not retried: a repeat would add it twice. */
159
+ createEmail(autoresponderId: number, params: CreateAutoresponderEmailParams): Promise<AutoresponderEmail>;
160
+ }
161
+
162
+ interface CreateCampaignParams {
6
163
  fromName: string;
7
164
  fromEmail: string;
8
165
  replyTo: string;
166
+ title: string;
9
167
  subject: string;
10
168
  htmlText: string;
11
169
  plainText?: string;
12
- to?: string[];
13
- recipientVariables?: Record<string, Record<string, any>>;
170
+ /** Encrypted list ids. Needed, with `segmentIds`, only to send or schedule. */
14
171
  listIds?: string[];
172
+ segmentIds?: (string | number)[];
173
+ excludeListIds?: string[];
174
+ excludeSegmentIds?: (string | number)[];
15
175
  queryString?: string;
16
- trackOpens?: 0 | 1 | 2;
17
- trackClicks?: 0 | 1 | 2;
176
+ trackOpens?: Tracking;
177
+ trackClicks?: Tracking;
178
+ /** Send immediately once created. */
179
+ send?: boolean;
180
+ /** Schedule instead, e.g. "June 15, 2027 6:05pm". Minutes must be a multiple of 5. */
18
181
  scheduleDateTime?: string;
182
+ /** e.g. "America/New_York". Falls back to the brand's timezone. */
19
183
  scheduleTimezone?: string;
20
184
  }
21
- export interface CreateCampaignOptions {
185
+ interface CreateCampaignResult {
186
+ outcome: 'created' | 'sending' | 'scheduled';
187
+ /**
188
+ * Only for a plain create: the sending and scheduling paths of create.php
189
+ * answer with bare prose and no id.
190
+ */
191
+ campaignId?: string;
192
+ }
193
+ /** As the brand's campaign list labels them. */
194
+ declare const CAMPAIGN_STATUSES: readonly ["draft", "scheduled", "preparing", "sending", "sent", "paused"];
195
+ type CampaignStatus = (typeof CAMPAIGN_STATUSES)[number];
196
+ /** One row of `campaigns.list()`. */
197
+ interface Campaign {
198
+ id: number;
199
+ title: string;
200
+ subject: string;
201
+ status: CampaignStatus;
202
+ from_name: string;
203
+ from_email: string;
204
+ reply_to: string;
205
+ recipients: number;
206
+ to_send: number;
207
+ /** Unix seconds. */
208
+ sent_at: number | null;
209
+ /** Unix seconds, only while scheduled. */
210
+ scheduled_at: number | null;
211
+ timezone: string | null;
212
+ /** `ui`, or `api` for campaigns created by `emails.send()`. */
213
+ source: string;
214
+ }
215
+ interface CampaignDetail extends Campaign {
216
+ preheader: string;
217
+ query_string: string;
218
+ track_opens: Tracking;
219
+ track_clicks: Tracking;
220
+ /** Encrypted, like the ids `lists.list()` returns. */
221
+ list_ids: string[];
222
+ exclude_list_ids: string[];
223
+ segment_ids: number[];
224
+ exclude_segment_ids: number[];
225
+ web_version: string;
226
+ /** Only with `includeContent`. */
227
+ html_text?: string;
228
+ plain_text?: string;
229
+ }
230
+ interface CampaignStats extends EngagementStats {
231
+ id: number;
232
+ status: CampaignStatus;
233
+ }
234
+ interface ListCampaignsParams {
235
+ /** 1-based. */
236
+ page?: number;
237
+ /** At most 100. */
238
+ limit?: number;
239
+ status?: CampaignStatus;
240
+ }
241
+ declare const MAX_CAMPAIGNS_PAGE = 100;
242
+ interface Recipients {
243
+ /** Encrypted list ids. At least one list or segment is required. */
244
+ listIds?: string[];
245
+ segmentIds?: (string | number)[];
246
+ excludeListIds?: string[];
247
+ excludeSegmentIds?: (string | number)[];
248
+ /** Run every check and count the recipients, but send nothing. */
249
+ dryRun?: boolean;
250
+ }
251
+ interface SendCampaignResult {
252
+ status: 'sending' | 'dry_run';
253
+ campaign_id: number;
254
+ /** Unique active subscribers the campaign goes (or would go) to. */
255
+ recipients: number;
256
+ }
257
+ interface ScheduleCampaignParams extends Recipients {
258
+ /** Anything PHP's strtotime() reads, e.g. "2027-06-15 18:05" or "June 15, 2027 6:05pm". */
259
+ at: string;
260
+ /** e.g. "Africa/Lagos". Defaults to the account's timezone. */
261
+ timezone?: string;
262
+ }
263
+ interface ScheduleCampaignResult {
264
+ status: 'scheduled' | 'dry_run';
265
+ campaign_id: number;
266
+ recipients: number;
267
+ /** Unix seconds. */
268
+ scheduled_at: number;
269
+ timezone: string;
270
+ }
271
+ /** test-send.php refuses more than this many addresses per request. */
272
+ declare const MAX_TEST_EMAILS = 5;
273
+ interface TestSendResult {
274
+ campaign_id: number;
275
+ /** One per address, in the order sent. */
276
+ results: {
277
+ email: string;
278
+ ok: boolean;
279
+ error: string | null;
280
+ }[];
281
+ }
282
+ /** Fields `campaigns.update()` can change. Only those given change. */
283
+ interface UpdateCampaignParams {
284
+ title?: string;
285
+ subject?: string;
286
+ preheader?: string;
287
+ fromName?: string;
288
+ fromEmail?: string;
289
+ replyTo?: string;
290
+ htmlText?: string;
291
+ plainText?: string;
292
+ queryString?: string;
293
+ trackOpens?: Tracking;
294
+ trackClicks?: Tracking;
295
+ }
296
+ declare const ACTIVITY_TYPES: readonly ["opens", "clicks", "bounces", "complaints", "unsubscribes"];
297
+ type ActivityType = (typeof ACTIVITY_TYPES)[number];
298
+ /** One subscriber's part in a campaign. `opens`/`country` only for opens, `clicks`/`links` only for clicks. */
299
+ interface CampaignActivity {
300
+ email: string;
301
+ name: string;
302
+ /** Encrypted, like the ids `lists.list()` returns. */
303
+ list_id: string;
304
+ opens?: number;
305
+ /** Where they first opened it, when known. */
306
+ country?: string | null;
307
+ clicks?: number;
308
+ links?: string[];
309
+ }
310
+ interface CampaignActivityPage {
311
+ campaign_id: number;
312
+ type: ActivityType;
313
+ page: number;
314
+ limit: number;
315
+ /** Subscribers across all pages. */
316
+ total: number;
317
+ activity: CampaignActivity[];
318
+ }
319
+ interface CampaignActivityParams {
320
+ type?: ActivityType;
321
+ page?: number;
322
+ /** At most 1000. */
323
+ limit?: number;
324
+ }
325
+ declare const MAX_ACTIVITY_PAGE = 1000;
326
+ declare class Campaigns {
327
+ private readonly transport;
328
+ constructor(transport: Transport);
329
+ /**
330
+ * `api/campaigns/create.php` creates a draft, and optionally sends or
331
+ * schedules it in the same call. To send a draft that already exists, see
332
+ * `send()`.
333
+ */
334
+ create(params: CreateCampaignParams): Promise<CreateCampaignResult>;
335
+ /** One page, newest first. An empty page is an empty array, not an error. */
336
+ list(params?: ListCampaignsParams): Promise<Campaign[]>;
337
+ /** Every page, fetched in order at the largest page size. */
338
+ listAll(params?: Omit<ListCampaignsParams, 'page' | 'limit'>): Promise<Campaign[]>;
339
+ get(campaignId: number, opts?: {
340
+ includeContent?: boolean;
341
+ }): Promise<CampaignDetail>;
342
+ stats(campaignId: number): Promise<CampaignStats>;
343
+ /**
344
+ * `api/campaigns/send.php` sends an existing draft. It only ever claims a
345
+ * draft, so a repeat after a lost response answers "Campaign has already been
346
+ * sent" rather than sending twice. It is still not retried: that answer
347
+ * would turn a success into a reported failure.
348
+ */
349
+ send(campaignId: number, recipients: Recipients): Promise<SendCampaignResult>;
350
+ /**
351
+ * Sends the campaign as a test to up to 5 addresses. Each request counts
352
+ * against a per-brand limit shared with the dashboard (20 an hour), so it is
353
+ * never retried.
354
+ */
355
+ testSend(campaignId: number, emails: string[]): Promise<TestSendResult>;
356
+ /**
357
+ * Schedules a draft, or moves a scheduled campaign. The recipients are
358
+ * reserved against the quota now and handed back by `unschedule()`.
359
+ */
360
+ schedule(campaignId: number, params: ScheduleCampaignParams): Promise<ScheduleCampaignResult>;
361
+ /** Back to a draft, with its quota reservation refunded. */
362
+ unschedule(campaignId: number): Promise<{
363
+ status: 'draft';
364
+ campaign_id: number;
365
+ }>;
366
+ /** Edits a draft or scheduled campaign and returns it as `get()` would. */
367
+ update(campaignId: number, changes: UpdateCampaignParams): Promise<CampaignDetail>;
368
+ /** A new draft copied from the campaign, in the same brand. */
369
+ duplicate(campaignId: number, opts?: {
370
+ title?: string;
371
+ }): Promise<CampaignDetail>;
372
+ /**
373
+ * Deletes the campaign with its links and attachments; a sent one loses its
374
+ * report. Refused while the campaign is preparing or sending.
375
+ */
376
+ delete(campaignId: number): Promise<{
377
+ campaign_id: number;
378
+ deleted: true;
379
+ }>;
380
+ /**
381
+ * A page of who opened, clicked, bounced, complained or unsubscribed. Opens
382
+ * and clicks are in the order subscribers first did them.
383
+ */
384
+ activity(campaignId: number, params?: CampaignActivityParams): Promise<CampaignActivityPage>;
385
+ /** Every page of one activity type, at the largest page size. */
386
+ activityAll(campaignId: number, type: ActivityType): Promise<CampaignActivity[]>;
387
+ }
388
+
389
+ /** send.php refuses more than this, and says so with a 400. */
390
+ declare const MAX_RECIPIENTS = 250;
391
+ interface SendEmailParams {
22
392
  fromName: string;
23
393
  fromEmail: string;
24
394
  replyTo: string;
25
- title: string;
26
395
  subject: string;
27
396
  htmlText: string;
28
397
  plainText?: string;
29
- listIds?: string;
30
- segmentIds?: string;
31
- excludeListIds?: string;
32
- excludeSegmentIds?: string;
398
+ /** Addresses to send to, at most 250. Without `listIds` these are added to a new list. */
399
+ to?: string[];
400
+ /** Per-recipient values, referenced in the body as `[recipient.first]`. */
401
+ recipientVariables?: Record<string, Record<string, unknown>>;
402
+ /** Encrypted list ids, as `lists.list()` returns them. */
403
+ listIds?: string[];
33
404
  queryString?: string;
34
- trackOpens?: 0 | 1 | 2;
35
- trackClicks?: 0 | 1 | 2;
36
- sendCampaign?: 0 | 1;
405
+ trackOpens?: Tracking;
406
+ trackClicks?: Tracking;
407
+ /** e.g. "June 15, 2027 6:05pm". */
37
408
  scheduleDateTime?: string;
409
+ /** e.g. "Africa/Lagos". Falls back to the brand's timezone. */
38
410
  scheduleTimezone?: string;
39
411
  }
40
- export interface List {
412
+ interface SendEmailResult {
413
+ message: string;
414
+ /** The campaign the send created behind the scenes. */
415
+ campaignId?: string;
416
+ }
417
+ interface SendTransactionalParams {
418
+ toEmail: string;
419
+ subject: string;
420
+ /** At least one of `htmlBody` and `plainBody` is required. */
421
+ htmlBody?: string;
422
+ plainBody?: string;
423
+ /** Each falls back to the brand's configured value when omitted. */
424
+ fromName?: string;
425
+ fromEmail?: string;
426
+ replyTo?: string;
427
+ }
428
+ interface SendTransactionalResult {
429
+ message: string;
430
+ messageId?: string;
431
+ }
432
+ declare class Emails {
433
+ private readonly transport;
434
+ constructor(transport: Transport);
435
+ /**
436
+ * `api/emails/send.php`: a bulk send to addresses or lists. It creates a
437
+ * campaign behind the scenes; the subject doubles as its title.
438
+ */
439
+ send(params: SendEmailParams): Promise<SendEmailResult>;
440
+ /**
441
+ * `api/emails/send-transactional.php`: one email, delivered immediately with
442
+ * nothing queued. For OTPs, password resets and receipts.
443
+ */
444
+ sendTransactional(params: SendTransactionalParams): Promise<SendTransactionalResult>;
445
+ }
446
+
447
+ interface List {
448
+ /**
449
+ * An encrypted value, not the number in the dashboard URL. Pass it back as
450
+ * given wherever a list id is wanted.
451
+ */
452
+ id: string;
453
+ name: string;
454
+ }
455
+ type OptIn = 'single' | 'double';
456
+ type CustomFieldType = 'text' | 'date';
457
+ interface CustomField {
458
+ /** As used in personalization tags: `[Name,fallback=]`. */
459
+ name: string;
460
+ /** As the API stores it. */
461
+ type: 'Text' | 'Date';
462
+ }
463
+ /** One list from `get()`, `create()`, `update()` or the custom-field methods. */
464
+ interface ListDetail extends List {
465
+ opt_in: OptIn;
466
+ /** `active` is who a campaign goes to: confirmed, and not unsubscribed, bounced or complained. */
467
+ subscribers: {
468
+ active: number;
469
+ unconfirmed: number;
470
+ unsubscribed: number;
471
+ bounced: number;
472
+ complained: number;
473
+ };
474
+ /** In the order subscribers' values are stored. */
475
+ custom_fields: CustomField[];
476
+ }
477
+ declare class Lists {
478
+ private readonly transport;
479
+ constructor(transport: Transport);
480
+ /** The brand's lists. An empty brand is an empty array, not an error. */
481
+ list(opts?: {
482
+ includeHidden?: boolean;
483
+ }): Promise<List[]>;
484
+ get(listId: string): Promise<ListDetail>;
485
+ /** Not retried: a repeat after a lost response would create a second list. */
486
+ create(params: {
487
+ name: string;
488
+ optIn?: OptIn;
489
+ }): Promise<ListDetail>;
490
+ update(listId: string, changes: {
491
+ name?: string;
492
+ optIn?: OptIn;
493
+ }): Promise<ListDetail>;
494
+ /**
495
+ * Deletes the list with its subscribers, segments, autoresponders and rules.
496
+ * Refused while a scheduled or sending campaign is going to it.
497
+ */
498
+ delete(listId: string): Promise<{
499
+ id: string;
500
+ deleted: true;
501
+ }>;
502
+ /**
503
+ * Subscribers a campaign to this list would reach.
504
+ *
505
+ * Anything but a bare integer is a failure and is thrown, never reported as
506
+ * 0: "this list is empty" is the one wrong answer a caller cannot detect.
507
+ */
508
+ activeSubscriberCount(listId: string): Promise<number>;
509
+ /** Adds a field; every subscriber gets an empty value for it. Returns the list with its fields. */
510
+ addCustomField(listId: string, params: {
511
+ name: string;
512
+ type?: CustomFieldType;
513
+ }): Promise<ListDetail>;
514
+ /**
515
+ * Renames a field, keeping subscribers' values. The autoresponders and
516
+ * segment conditions that use it follow; tags already in campaigns keep the
517
+ * old name.
518
+ */
519
+ renameCustomField(listId: string, name: string, newName: string): Promise<ListDetail>;
520
+ /** Deletes a field and every subscriber's value for it. Refused while an autoresponder or segment uses it. */
521
+ deleteCustomField(listId: string, name: string): Promise<ListDetail>;
522
+ }
523
+
524
+ interface Brand {
41
525
  id: string;
42
526
  name: string;
43
527
  }
44
- export declare class SkrybeSDK {
45
- private client;
46
- private apiKey;
47
- constructor(config: SkrybeConfig);
48
- private createFormData;
49
- sendEmail(options: SendEmailOptions): Promise<string>;
50
- createCampaign(options: CreateCampaignOptions): Promise<string>;
51
- getLists(includeHidden?: boolean): Promise<List[]>;
528
+ declare class Brands {
529
+ private readonly transport;
530
+ constructor(transport: Transport);
531
+ /**
532
+ * The key's own brand: a brand key gets exactly one row. Empty, not an error,
533
+ * when there is none.
534
+ */
535
+ list(): Promise<Brand[]>;
536
+ }
537
+ declare class Stats {
538
+ private readonly transport;
539
+ constructor(transport: Transport);
540
+ /**
541
+ * Emails sent across the whole installation (campaigns, autoresponders and
542
+ * transactional), not a per-brand figure. Public and cached for 5 minutes.
543
+ */
544
+ totalEmailsSent(): Promise<number>;
545
+ }
546
+
547
+ interface SubscriberRef {
548
+ /** The encrypted list id, as `lists.list()` returns it. */
549
+ listId: string;
550
+ email: string;
551
+ }
552
+ interface SubscribeParams extends SubscriberRef {
553
+ name?: string;
554
+ /** Two-letter ISO 3166-1 code, e.g. NG. */
555
+ country?: string;
556
+ ipAddress?: string;
557
+ referrer?: string;
558
+ /** Record GDPR consent for an EU signup. */
559
+ gdpr?: boolean;
560
+ /** Add to a double opt-in list as single opt-in, skipping confirmation. */
561
+ silent?: boolean;
562
+ /**
563
+ * Custom fields, keyed by the name the list shows, e.g. `Birthday` for a
564
+ * field whose tag is `[Birthday,fallback=]`. Spaces are taken out before
565
+ * sending, as the server matches on.
566
+ */
567
+ fields?: Record<string, string>;
568
+ }
569
+ type SubscribeOutcome = 'subscribed' | 'already_subscribed';
570
+ /** Every subscriber is in exactly one of these; `active` is who a campaign sends to. */
571
+ declare const SUBSCRIBER_STATUSES: readonly ["active", "unconfirmed", "unsubscribed", "bounced", "complained"];
572
+ type SubscriberStatus = (typeof SUBSCRIBER_STATUSES)[number];
573
+ interface Subscriber {
574
+ email: string;
575
+ name: string;
576
+ status: SubscriberStatus;
577
+ /** Unix seconds. */
578
+ joined_at: number | null;
579
+ /** By field name, in the list's order. Dates are YYYY-MM-DD; empty values are null. */
580
+ custom_fields: Record<string, string | null>;
581
+ }
582
+ interface SubscribersPage {
583
+ list_id: string;
584
+ status: SubscriberStatus | null;
585
+ page: number;
586
+ limit: number;
587
+ /** Subscribers matching the status filter, across all pages. */
588
+ total: number;
589
+ subscribers: Subscriber[];
590
+ }
591
+ interface ListSubscribersParams {
592
+ status?: SubscriberStatus;
593
+ /** 1-based. */
594
+ page?: number;
595
+ /** At most 1000. */
596
+ limit?: number;
597
+ }
598
+ declare const MAX_SUBSCRIBERS_PAGE = 1000;
599
+ declare class Subscribers {
600
+ private readonly transport;
601
+ constructor(transport: Transport);
602
+ /**
603
+ * Adds an address to a list. Posting one that is already there updates its
604
+ * name and custom fields, and resolves `already_subscribed` rather than
605
+ * throwing, so re-running an import is safe.
606
+ */
607
+ subscribe(params: SubscribeParams): Promise<SubscribeOutcome>;
608
+ /** Marks the address unsubscribed from the list. */
609
+ unsubscribe(ref: SubscriberRef): Promise<void>;
610
+ /** Removes the subscriber outright, rather than marking it unsubscribed. */
611
+ delete(ref: SubscriberRef): Promise<void>;
612
+ /**
613
+ * One of Subscribed, Unsubscribed, Unconfirmed, Bounced, Soft bounced or
614
+ * Complained. Returned as given, so a status added server-side surfaces
615
+ * rather than being rejected.
616
+ */
617
+ status(ref: SubscriberRef): Promise<string>;
618
+ /** One page of a list's subscribers, oldest first. */
619
+ list(listId: string, params?: ListSubscribersParams): Promise<SubscribersPage>;
620
+ /**
621
+ * Every page, at the largest page size. Oldest first, so someone joining
622
+ * mid-way lands on a later page instead of shifting the ones already read.
623
+ */
624
+ listAll(listId: string, params?: {
625
+ status?: SubscriberStatus;
626
+ }): Promise<Subscriber[]>;
627
+ }
628
+
629
+ /** The editor the Skrybe dashboard opens a template in. */
630
+ type TemplateEditor = 'html' | 'dragdrop';
631
+ interface Template {
632
+ id: number;
633
+ name: string;
634
+ from_name: string;
635
+ from_email: string;
636
+ reply_to: string;
637
+ editor: TemplateEditor;
638
+ /** Only from `get()` with `includeContent`, and from create and update. */
639
+ html_text?: string;
640
+ plain_text?: string;
641
+ }
642
+ interface TemplateFields {
643
+ name?: string;
644
+ htmlText?: string;
645
+ plainText?: string;
646
+ fromName?: string;
647
+ fromEmail?: string;
648
+ replyTo?: string;
649
+ editor?: TemplateEditor;
650
+ }
651
+ declare class Templates {
652
+ private readonly transport;
653
+ constructor(transport: Transport);
654
+ /** The brand's templates by name, without their HTML. */
655
+ list(): Promise<Template[]>;
656
+ get(templateId: number, opts?: {
657
+ includeContent?: boolean;
658
+ }): Promise<Template>;
659
+ /** Not retried: a repeat would create a second template. */
660
+ create(fields: TemplateFields & {
661
+ name: string;
662
+ htmlText: string;
663
+ }): Promise<Template>;
664
+ /** Only the fields given change. */
665
+ update(templateId: number, fields: TemplateFields): Promise<Template>;
666
+ /** Campaigns already made from it are unaffected. */
667
+ delete(templateId: number): Promise<{
668
+ template_id: number;
669
+ deleted: true;
670
+ }>;
671
+ }
672
+
673
+ declare const DEFAULT_BASE_URL = "https://dashboard.skry.be";
674
+ interface SkrybeOptions {
675
+ /** A brand's API key, from Settings in the Skrybe dashboard. Keep it server-side. */
676
+ apiKey: string;
677
+ /** Defaults to https://dashboard.skry.be. */
678
+ baseUrl?: string;
679
+ /** Per attempt. Defaults to 30 seconds. */
680
+ timeoutMs?: number;
681
+ /**
682
+ * Extra attempts for reads after a dropped connection or a 429/502/503/504.
683
+ * Defaults to 2. Writes are never retried: the API has no idempotency keys,
684
+ * so a retried send could go out twice.
685
+ */
686
+ maxRetries?: number;
687
+ /**
688
+ * The fetch to use, defaulting to the global one. Pass your own to route
689
+ * through a proxy or trust a corporate CA, e.g. undici's fetch with a
690
+ * dispatcher. There is deliberately no option to skip TLS verification.
691
+ */
692
+ fetch?: FetchLike;
693
+ }
694
+ declare class Skrybe {
695
+ readonly emails: Emails;
696
+ readonly campaigns: Campaigns;
697
+ readonly lists: Lists;
698
+ readonly subscribers: Subscribers;
699
+ readonly templates: Templates;
700
+ readonly autoresponders: Autoresponders;
701
+ readonly brands: Brands;
702
+ readonly stats: Stats;
703
+ constructor(options: SkrybeOptions);
704
+ }
705
+
706
+ /**
707
+ * The Skrybe API answers most failures with HTTP 200 and a prose sentence.
708
+ * Only api/emails/send.php, api/emails/send-transactional.php and
709
+ * api/stats/emails-sent.php use real status codes.
710
+ *
711
+ * Everything here exists to turn that into something code can branch on: every
712
+ * failure is a `SkrybeError` with a stable `code`, and its subclass says what
713
+ * kind of failure it is.
714
+ */
715
+ /**
716
+ * - `authentication`: the API key is missing or wrong.
717
+ * - `invalid_request`: the request itself is wrong; retrying it unchanged will fail again.
718
+ * - `rate_limit`: a quota or rate limit was hit.
719
+ * - `connection`: the API could not be reached, or did not answer in time.
720
+ * - `api`: anything else the API refused or got wrong.
721
+ */
722
+ type SkrybeErrorType = 'authentication' | 'invalid_request' | 'rate_limit' | 'connection' | 'api';
723
+ interface SkrybeErrorOptions {
724
+ /** What to do about it, when we know. */
725
+ hint?: string;
726
+ /** HTTP status of the response, when there was one. */
727
+ status?: number;
728
+ /** Raw response body, kept for debugging odd endpoints. */
729
+ raw?: string;
730
+ cause?: unknown;
731
+ }
732
+ declare class SkrybeError extends Error {
733
+ /** Stable machine-readable slug, e.g. `list_not_found`. */
734
+ readonly code: string;
735
+ readonly type: SkrybeErrorType;
736
+ readonly hint?: string;
737
+ readonly status?: number;
738
+ readonly raw?: string;
739
+ constructor(type: SkrybeErrorType, code: string, message: string, opts?: SkrybeErrorOptions);
740
+ }
741
+ declare class AuthenticationError extends SkrybeError {
742
+ constructor(code: string, message: string, opts?: SkrybeErrorOptions);
743
+ }
744
+ declare class InvalidRequestError extends SkrybeError {
745
+ constructor(code: string, message: string, opts?: SkrybeErrorOptions);
746
+ }
747
+ declare class RateLimitError extends SkrybeError {
748
+ constructor(code: string, message: string, opts?: SkrybeErrorOptions);
749
+ }
750
+ declare class ConnectionError extends SkrybeError {
751
+ constructor(code: string, message: string, opts?: SkrybeErrorOptions);
752
+ }
753
+ declare class ApiError extends SkrybeError {
754
+ constructor(code: string, message: string, opts?: SkrybeErrorOptions);
52
755
  }
756
+
757
+ /**
758
+ * Sent in the User-Agent. A constant rather than read from package.json, which
759
+ * not every runtime can load; scripts/release.mjs keeps the two in step and a
760
+ * test fails if they drift.
761
+ */
762
+ declare const VERSION = "2.0.0";
763
+
764
+ export { ACTIVITY_TYPES, type ActivityType, ApiError, AuthenticationError, type Autoresponder, type AutoresponderEmail, type AutoresponderEmailStats, type AutoresponderType, type AutoresponderWithEmails, type Brand, CAMPAIGN_STATUSES, type Campaign, type CampaignActivity, type CampaignActivityPage, type CampaignActivityParams, type CampaignDetail, type CampaignStats, type CampaignStatus, ConnectionError, type CreateAutoresponderEmailParams, type CreateAutoresponderParams, type CreateCampaignParams, type CreateCampaignResult, type CustomField, type CustomFieldType, DEFAULT_BASE_URL, type EngagementStats, type FetchLike, InvalidRequestError, type List, type ListCampaignsParams, type ListDetail, type ListSubscribersParams, MAX_ACTIVITY_PAGE, MAX_CAMPAIGNS_PAGE, MAX_RECIPIENTS, MAX_SUBSCRIBERS_PAGE, MAX_TEST_EMAILS, type OptIn, RateLimitError, type Recipients, SUBSCRIBER_STATUSES, type ScheduleCampaignParams, type ScheduleCampaignResult, type SendCampaignResult, type SendEmailParams, type SendEmailResult, type SendTransactionalParams, type SendTransactionalResult, Skrybe, SkrybeError, type SkrybeErrorType, type SkrybeOptions, type SubscribeOutcome, type SubscribeParams, type Subscriber, type SubscriberRef, type SubscriberStatus, type SubscribersPage, type Template, type TemplateEditor, type TemplateFields, type TestSendResult, type Tracking, type UpdateCampaignParams, VERSION };