coss 0.2.0 → 0.4.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/README.md +40 -10
- package/dist/device.d.ts +11 -0
- package/dist/device.js +71 -0
- package/dist/index.d.ts +1211 -143
- package/dist/index.js +558 -25
- package/package.json +15 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ const API_VERSION = "2026-10-10";
|
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/version.ts
|
|
11
11
|
/** SDK version, sent in the User-Agent. A test keeps it equal to package.json's `version`. */
|
|
12
|
-
const VERSION = "0.
|
|
12
|
+
const VERSION = "0.4.0";
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/core/errors.ts
|
|
15
15
|
const API_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -49,7 +49,7 @@ function errorFromResponse(status, statusText, body, headerRequestId) {
|
|
|
49
49
|
if (!isRecord(envelope) || typeof envelope.message !== "string") return {
|
|
50
50
|
type: "api_error",
|
|
51
51
|
code: "unexpected_response",
|
|
52
|
-
message: `The
|
|
52
|
+
message: `The coss.com API returned an unexpected HTTP ${status}${statusText ? ` (${statusText})` : ""} response.`,
|
|
53
53
|
status,
|
|
54
54
|
request_id: headerRequestId
|
|
55
55
|
};
|
|
@@ -208,7 +208,7 @@ var HttpClient = class {
|
|
|
208
208
|
};
|
|
209
209
|
const timeoutMessage = `Request timed out after ${this.#timeout}ms.`;
|
|
210
210
|
return {
|
|
211
|
-
result: failed(timeoutSignal.aborted ? clientError("timeout_error", "request_timeout", timeoutMessage) : clientError("connection_error", "network_error", `Could not reach the
|
|
211
|
+
result: failed(timeoutSignal.aborted ? clientError("timeout_error", "request_timeout", timeoutMessage) : clientError("connection_error", "network_error", `Could not reach the coss.com API at ${this.#baseUrl}: ${describe(cause)}`)),
|
|
212
212
|
retry: true,
|
|
213
213
|
retryAfterMs: null
|
|
214
214
|
};
|
|
@@ -249,7 +249,7 @@ var HttpClient = class {
|
|
|
249
249
|
result: failed({
|
|
250
250
|
type: "api_error",
|
|
251
251
|
code: "invalid_response",
|
|
252
|
-
message: `The
|
|
252
|
+
message: `The coss.com API returned an HTTP ${response.status} response that is not valid JSON.`,
|
|
253
253
|
status: response.status,
|
|
254
254
|
request_id: requestId
|
|
255
255
|
}),
|
|
@@ -266,6 +266,24 @@ function failed(error) {
|
|
|
266
266
|
};
|
|
267
267
|
}
|
|
268
268
|
//#endregion
|
|
269
|
+
//#region src/resources/api-key.ts
|
|
270
|
+
/** `coss.apiKey`: the API key the client was created with. */
|
|
271
|
+
var ApiKeyResource = class {
|
|
272
|
+
#http;
|
|
273
|
+
/** @internal Use `new Coss().apiKey`. */
|
|
274
|
+
constructor(http) {
|
|
275
|
+
this.#http = http;
|
|
276
|
+
}
|
|
277
|
+
/** Revokes the client's own API key, at once: every later request with it fails. */
|
|
278
|
+
revoke(options) {
|
|
279
|
+
return this.#http.request({
|
|
280
|
+
method: "DELETE",
|
|
281
|
+
path: "/v1/api_key",
|
|
282
|
+
signal: options?.signal
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
//#endregion
|
|
269
287
|
//#region src/resources/list-query.ts
|
|
270
288
|
/** One query parameter: lists are comma-joined (an empty one is left out), dates ISO 8601. */
|
|
271
289
|
function joined(value) {
|
|
@@ -284,7 +302,7 @@ function listQuery(params) {
|
|
|
284
302
|
//#region src/resources/audit-events.ts
|
|
285
303
|
/**
|
|
286
304
|
* `coss.auditEvents`: the organization's audit log, newest first: changes made by members, API
|
|
287
|
-
* keys and
|
|
305
|
+
* keys and coss.com support. Organization-wide, whichever environment the key belongs to.
|
|
288
306
|
*/
|
|
289
307
|
var AuditEventsResource = class {
|
|
290
308
|
#http;
|
|
@@ -387,6 +405,59 @@ var BillingResource = class {
|
|
|
387
405
|
}
|
|
388
406
|
};
|
|
389
407
|
//#endregion
|
|
408
|
+
//#region src/resources/dmarc.ts
|
|
409
|
+
const dmarcPath = (domainId) => `/v1/domains/${encodeURIComponent(domainId)}/dmarc`;
|
|
410
|
+
/** `coss.domains.dmarc.reports`: the aggregate reports received about a domain. */
|
|
411
|
+
var DmarcReportsResource = class {
|
|
412
|
+
#http;
|
|
413
|
+
/** @internal Use `new Coss().domains.dmarc.reports`. */
|
|
414
|
+
constructor(http) {
|
|
415
|
+
this.#http = http;
|
|
416
|
+
}
|
|
417
|
+
/** Lists the domain's reports, newest first, without their rows. */
|
|
418
|
+
list(domainId, params = {}, options) {
|
|
419
|
+
return this.#http.request({
|
|
420
|
+
method: "GET",
|
|
421
|
+
path: `${dmarcPath(domainId)}/reports`,
|
|
422
|
+
query: listQuery(params),
|
|
423
|
+
signal: options?.signal
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/** Retrieves one report with its rows. */
|
|
427
|
+
get(domainId, reportId, options) {
|
|
428
|
+
return this.#http.request({
|
|
429
|
+
method: "GET",
|
|
430
|
+
path: `${dmarcPath(domainId)}/reports/${encodeURIComponent(reportId)}`,
|
|
431
|
+
signal: options?.signal
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
/**
|
|
436
|
+
* `coss.domains.dmarc`: a domain's DMARC record, the guided path from `p=none` to `p=reject`, and
|
|
437
|
+
* the aggregate reports mailbox providers send to its `report_address`.
|
|
438
|
+
*/
|
|
439
|
+
var DmarcResource = class {
|
|
440
|
+
#http;
|
|
441
|
+
/** The domain's aggregate reports. */
|
|
442
|
+
reports;
|
|
443
|
+
/** @internal Use `new Coss().domains.dmarc`. */
|
|
444
|
+
constructor(http) {
|
|
445
|
+
this.#http = http;
|
|
446
|
+
this.reports = new DmarcReportsResource(http);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* The domain's DMARC status: its published record, the record to publish next and whether
|
|
450
|
+
* the last 14 days of reports say that is safe (`ready`), and a summary of those reports.
|
|
451
|
+
*/
|
|
452
|
+
get(domainId, options) {
|
|
453
|
+
return this.#http.request({
|
|
454
|
+
method: "GET",
|
|
455
|
+
path: dmarcPath(domainId),
|
|
456
|
+
signal: options?.signal
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
//#endregion
|
|
390
461
|
//#region src/resources/receiving-domains.ts
|
|
391
462
|
const receivingPath = (domainId) => `/v1/domains/${encodeURIComponent(domainId)}/receiving`;
|
|
392
463
|
/**
|
|
@@ -471,11 +542,14 @@ var DomainsResource = class {
|
|
|
471
542
|
tracking;
|
|
472
543
|
/** Each domain's receiving hostname: start and stop receiving email at it. */
|
|
473
544
|
receiving;
|
|
545
|
+
/** Each domain's DMARC status, guidance and aggregate reports. */
|
|
546
|
+
dmarc;
|
|
474
547
|
/** @internal Use `new Coss().domains`. */
|
|
475
548
|
constructor(http) {
|
|
476
549
|
this.#http = http;
|
|
477
550
|
this.tracking = new TrackingDomainsResource(http);
|
|
478
551
|
this.receiving = new ReceivingDomainsResource(http);
|
|
552
|
+
this.dmarc = new DmarcResource(http);
|
|
479
553
|
}
|
|
480
554
|
/**
|
|
481
555
|
* Adds a domain and returns the DNS records to publish (`data.records`). The records are
|
|
@@ -529,6 +603,230 @@ var DomainsResource = class {
|
|
|
529
603
|
}
|
|
530
604
|
};
|
|
531
605
|
//#endregion
|
|
606
|
+
//#region src/react.ts
|
|
607
|
+
/**
|
|
608
|
+
* React Email: a send's `react` is rendered into `html` (and `text`, unless given) by
|
|
609
|
+
* `@react-email/render`, an optional peer dependency imported only when a send has `react`, so the
|
|
610
|
+
* SDK itself keeps zero dependencies.
|
|
611
|
+
*/
|
|
612
|
+
let renderer;
|
|
613
|
+
/** Imports `@react-email/render` once; `null` when it isn't installed. */
|
|
614
|
+
function loadReactEmailRenderer() {
|
|
615
|
+
renderer ??= (async () => {
|
|
616
|
+
try {
|
|
617
|
+
return await import("@react-email/render");
|
|
618
|
+
} catch {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
})();
|
|
622
|
+
return renderer;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Turns `react` into `html` and `text`; params without it pass through. Rendering mistakes come back
|
|
626
|
+
* as `invalid_request_error`s naming `param`, like the API's validation errors, never thrown.
|
|
627
|
+
*/
|
|
628
|
+
async function renderReactEmail(params, param, load = loadReactEmailRenderer) {
|
|
629
|
+
const { react, ...body } = params;
|
|
630
|
+
if (react === void 0) return {
|
|
631
|
+
body,
|
|
632
|
+
error: null
|
|
633
|
+
};
|
|
634
|
+
if (body.template_id !== void 0) return failure("validation_error", "Pass either `react` or `template_id`, not both.", param);
|
|
635
|
+
if (body.html !== void 0) return failure("validation_error", "Pass either `react` or `html`, not both.", param);
|
|
636
|
+
const renderer = await load();
|
|
637
|
+
if (!renderer) return failure("react_renderer_missing", "Sending `react` needs the `@react-email/render` package: install it next to `coss`.", param);
|
|
638
|
+
try {
|
|
639
|
+
const html = await renderer.render(react);
|
|
640
|
+
if (body.text !== void 0) return {
|
|
641
|
+
body: {
|
|
642
|
+
...body,
|
|
643
|
+
html
|
|
644
|
+
},
|
|
645
|
+
error: null
|
|
646
|
+
};
|
|
647
|
+
const text = renderer.toPlainText ? renderer.toPlainText(html) : await renderer.render(react, { plainText: true });
|
|
648
|
+
return {
|
|
649
|
+
body: {
|
|
650
|
+
...body,
|
|
651
|
+
html,
|
|
652
|
+
text
|
|
653
|
+
},
|
|
654
|
+
error: null
|
|
655
|
+
};
|
|
656
|
+
} catch (cause) {
|
|
657
|
+
return failure("react_render_failed", `Rendering \`react\` failed: ${cause instanceof Error ? cause.message : String(cause)}`, param);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function failure(code, message, param) {
|
|
661
|
+
return {
|
|
662
|
+
body: null,
|
|
663
|
+
error: {
|
|
664
|
+
...clientError("invalid_request_error", code, message),
|
|
665
|
+
param
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/resources/dedicated-ips.ts
|
|
671
|
+
/**
|
|
672
|
+
* `coss.email.dedicatedIps`: the organization's own sending IPs (the Dedicated IPs add-on),
|
|
673
|
+
* managed and warmed up automatically.
|
|
674
|
+
*/
|
|
675
|
+
var DedicatedIpsResource = class {
|
|
676
|
+
#http;
|
|
677
|
+
/** @internal Use `new Coss().email.dedicatedIps`. */
|
|
678
|
+
constructor(http) {
|
|
679
|
+
this.#http = http;
|
|
680
|
+
}
|
|
681
|
+
/** The pool's status and IPs (`not_enabled` without the add-on). */
|
|
682
|
+
get(options) {
|
|
683
|
+
return this.#http.request({
|
|
684
|
+
method: "GET",
|
|
685
|
+
path: "/v1/dedicated_ips",
|
|
686
|
+
signal: options?.signal
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region src/resources/email-templates.ts
|
|
692
|
+
const templatePath = (template) => `/v1/email_templates/${encodeURIComponent(template)}`;
|
|
693
|
+
/** `coss.email.templates.versions`: a template's published versions. */
|
|
694
|
+
var EmailTemplateVersionsResource = class {
|
|
695
|
+
#http;
|
|
696
|
+
/** @internal Use `new Coss().email.templates.versions`. */
|
|
697
|
+
constructor(http) {
|
|
698
|
+
this.#http = http;
|
|
699
|
+
}
|
|
700
|
+
/** Published versions, newest first. */
|
|
701
|
+
list(template, params = {}, options) {
|
|
702
|
+
return this.#http.request({
|
|
703
|
+
method: "GET",
|
|
704
|
+
path: `${templatePath(template)}/versions`,
|
|
705
|
+
query: listQuery(params),
|
|
706
|
+
signal: options?.signal
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
get(template, version, options) {
|
|
710
|
+
return this.#http.request({
|
|
711
|
+
method: "GET",
|
|
712
|
+
path: `${templatePath(template)}/versions/${version}`,
|
|
713
|
+
signal: options?.signal
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Replaces the draft with this version's content; publish the draft to send it again. Never
|
|
718
|
+
* automatically retried: every restore is a new draft revision, so a repeat could overwrite an
|
|
719
|
+
* edit saved in between.
|
|
720
|
+
*/
|
|
721
|
+
restore(template, version, options) {
|
|
722
|
+
return this.#http.request({
|
|
723
|
+
method: "POST",
|
|
724
|
+
path: `${templatePath(template)}/versions/${version}/restore`,
|
|
725
|
+
retry: false,
|
|
726
|
+
signal: options?.signal
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
/**
|
|
731
|
+
* `coss.email.templates`: the environment's email templates. Templates are addressed by id or
|
|
732
|
+
* alias. Edit the draft, publish it, then send with `coss.email.send({ template_id, variables })`.
|
|
733
|
+
*/
|
|
734
|
+
var EmailTemplatesResource = class {
|
|
735
|
+
#http;
|
|
736
|
+
/** Published versions. */
|
|
737
|
+
versions;
|
|
738
|
+
/** @internal Use `new Coss().email.templates`. */
|
|
739
|
+
constructor(http) {
|
|
740
|
+
this.#http = http;
|
|
741
|
+
this.versions = new EmailTemplateVersionsResource(http);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Creates a template and its draft. Never automatically retried: the API doesn't deduplicate it
|
|
745
|
+
* (a template with an `alias` can't be created twice, though).
|
|
746
|
+
*/
|
|
747
|
+
create(params, options) {
|
|
748
|
+
return this.#http.request({
|
|
749
|
+
method: "POST",
|
|
750
|
+
path: "/v1/email_templates",
|
|
751
|
+
retry: false,
|
|
752
|
+
body: params,
|
|
753
|
+
idempotencyKey: options?.idempotencyKey,
|
|
754
|
+
signal: options?.signal
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
/** The environment's templates, newest first. */
|
|
758
|
+
list(params = {}, options) {
|
|
759
|
+
return this.#http.request({
|
|
760
|
+
method: "GET",
|
|
761
|
+
path: "/v1/email_templates",
|
|
762
|
+
query: listQuery(params),
|
|
763
|
+
signal: options?.signal
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
get(template, options) {
|
|
767
|
+
return this.#http.request({
|
|
768
|
+
method: "GET",
|
|
769
|
+
path: templatePath(template),
|
|
770
|
+
signal: options?.signal
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/** Changes its name, alias or description. */
|
|
774
|
+
update(template, params, options) {
|
|
775
|
+
return this.#http.request({
|
|
776
|
+
method: "PATCH",
|
|
777
|
+
path: templatePath(template),
|
|
778
|
+
body: params,
|
|
779
|
+
signal: options?.signal
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
/** Deletes it with every version. Emails already sent keep their content. */
|
|
783
|
+
delete(template, options) {
|
|
784
|
+
return this.#http.request({
|
|
785
|
+
method: "DELETE",
|
|
786
|
+
path: templatePath(template),
|
|
787
|
+
signal: options?.signal
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
/** The draft, with `problems` that keep it from being published. */
|
|
791
|
+
getDraft(template, options) {
|
|
792
|
+
return this.#http.request({
|
|
793
|
+
method: "GET",
|
|
794
|
+
path: `${templatePath(template)}/draft`,
|
|
795
|
+
signal: options?.signal
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Replaces the whole draft. Sends keep using the published version until you publish. Never
|
|
800
|
+
* automatically retried: every save is a new revision, so a repeat whose first attempt was saved
|
|
801
|
+
* would fail with `template_revision_mismatch` (or, without `revision`, overwrite an edit saved
|
|
802
|
+
* in between). Read the draft again before retrying.
|
|
803
|
+
*/
|
|
804
|
+
updateDraft(template, params, options) {
|
|
805
|
+
return this.#http.request({
|
|
806
|
+
method: "PUT",
|
|
807
|
+
path: `${templatePath(template)}/draft`,
|
|
808
|
+
retry: false,
|
|
809
|
+
body: params,
|
|
810
|
+
signal: options?.signal
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Publishes the draft as the next version, which sends use from then on. Automatically retried
|
|
815
|
+
* only with a `revision`: a draft without changes since the last publish returns that version,
|
|
816
|
+
* and one saved again since is refused. Without it, a retry could publish someone else's newer
|
|
817
|
+
* edits as yet another version.
|
|
818
|
+
*/
|
|
819
|
+
publish(template, params = {}, options) {
|
|
820
|
+
return this.#http.request({
|
|
821
|
+
method: "POST",
|
|
822
|
+
path: `${templatePath(template)}/publish`,
|
|
823
|
+
retry: params.revision !== void 0,
|
|
824
|
+
body: params,
|
|
825
|
+
signal: options?.signal
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
//#endregion
|
|
532
830
|
//#region src/resources/received-emails.ts
|
|
533
831
|
const emailPath = (id) => `/v1/received_emails/${encodeURIComponent(id)}`;
|
|
534
832
|
/**
|
|
@@ -711,8 +1009,8 @@ var SuppressionResource = class {
|
|
|
711
1009
|
const categoryPath = (category) => `/v1/tracking_settings/categories/${encodeURIComponent(category)}`;
|
|
712
1010
|
/**
|
|
713
1011
|
* `coss.email.tracking`: which emails of the API key's environment have their opens and clicks
|
|
714
|
-
* tracked. Emails keep the settings that applied when they
|
|
715
|
-
* option wins over them.
|
|
1012
|
+
* tracked and get one-click unsubscribe headers. Emails keep the settings that applied when they
|
|
1013
|
+
* were sent; a send's own `tracking` option wins over them.
|
|
716
1014
|
*/
|
|
717
1015
|
var TrackingResource = class {
|
|
718
1016
|
#http;
|
|
@@ -780,24 +1078,37 @@ var EmailResource = class {
|
|
|
780
1078
|
tracking;
|
|
781
1079
|
/** Emails the environment received. */
|
|
782
1080
|
received;
|
|
1081
|
+
/** The environment's email templates. */
|
|
1082
|
+
templates;
|
|
1083
|
+
/** The organization's dedicated IPs. */
|
|
1084
|
+
dedicatedIps;
|
|
783
1085
|
/** @internal Use `new Coss().email`. */
|
|
784
1086
|
constructor(http) {
|
|
785
1087
|
this.#http = http;
|
|
786
1088
|
this.suppressions = new SuppressionResource(http);
|
|
787
1089
|
this.tracking = new TrackingResource(http);
|
|
788
1090
|
this.received = new ReceivedEmailsResource(http);
|
|
1091
|
+
this.templates = new EmailTemplatesResource(http);
|
|
1092
|
+
this.dedicatedIps = new DedicatedIpsResource(http);
|
|
789
1093
|
}
|
|
790
1094
|
/**
|
|
791
|
-
* Sends an email
|
|
1095
|
+
* Sends an email, with its own subject and body or from a published template (`template_id`
|
|
1096
|
+
* and `variables`). It is returned `queued` and handed to the delivery provider moments later:
|
|
792
1097
|
* `get` it or listen for the `email.sent` / `email.failed` webhooks for the outcome. Safe to
|
|
793
1098
|
* retry: the request always carries an `Idempotency-Key` (yours, or a generated one reused
|
|
794
|
-
* across automatic retries), so the API never sends it twice.
|
|
1099
|
+
* across automatic retries), so the API never sends it twice. A `react` component is rendered
|
|
1100
|
+
* once, before the first attempt.
|
|
795
1101
|
*/
|
|
796
|
-
send(params, options) {
|
|
1102
|
+
async send(params, options) {
|
|
1103
|
+
const { body, error } = await renderReactEmail(params, "react");
|
|
1104
|
+
if (error) return {
|
|
1105
|
+
data: null,
|
|
1106
|
+
error
|
|
1107
|
+
};
|
|
797
1108
|
return this.#http.request({
|
|
798
1109
|
method: "POST",
|
|
799
1110
|
path: "/v1/emails",
|
|
800
|
-
body
|
|
1111
|
+
body,
|
|
801
1112
|
idempotencyKey: options?.idempotencyKey,
|
|
802
1113
|
signal: options?.signal
|
|
803
1114
|
});
|
|
@@ -808,15 +1119,32 @@ var EmailResource = class {
|
|
|
808
1119
|
* recipients count against the send quota together. Safe to retry: one `Idempotency-Key` covers
|
|
809
1120
|
* the whole batch. Larger lists need several calls, each with its own key.
|
|
810
1121
|
*/
|
|
811
|
-
sendBatch(emails, options) {
|
|
1122
|
+
async sendBatch(emails, options) {
|
|
1123
|
+
const rendered = await Promise.all(emails.map((email, index) => renderReactEmail(email, `emails.${index}.react`)));
|
|
1124
|
+
const bodies = [];
|
|
1125
|
+
for (const { body, error } of rendered) {
|
|
1126
|
+
if (error) return {
|
|
1127
|
+
data: null,
|
|
1128
|
+
error
|
|
1129
|
+
};
|
|
1130
|
+
bodies.push(body);
|
|
1131
|
+
}
|
|
812
1132
|
return this.#http.request({
|
|
813
1133
|
method: "POST",
|
|
814
1134
|
path: "/v1/emails/batch",
|
|
815
|
-
body: { emails },
|
|
1135
|
+
body: { emails: bodies },
|
|
816
1136
|
idempotencyKey: options?.idempotencyKey,
|
|
817
1137
|
signal: options?.signal
|
|
818
1138
|
});
|
|
819
1139
|
}
|
|
1140
|
+
/** Retrieves a batch sent by `sendBatch`, with its emails' current statuses in request order. */
|
|
1141
|
+
getBatch(id, options) {
|
|
1142
|
+
return this.#http.request({
|
|
1143
|
+
method: "GET",
|
|
1144
|
+
path: `/v1/emails/batch/${encodeURIComponent(id)}`,
|
|
1145
|
+
signal: options?.signal
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
820
1148
|
/** Retrieves an email sent from the same environment as the API key. */
|
|
821
1149
|
get(id, options) {
|
|
822
1150
|
return this.#http.request({
|
|
@@ -826,6 +1154,19 @@ var EmailResource = class {
|
|
|
826
1154
|
});
|
|
827
1155
|
}
|
|
828
1156
|
/**
|
|
1157
|
+
* Lists emails sent from the same environment as the API key, newest first and without their
|
|
1158
|
+
* content (`get` one for `html`, `text`, `headers` and `attachments`). Pass the last `id` as
|
|
1159
|
+
* `after` while `has_more` is true.
|
|
1160
|
+
*/
|
|
1161
|
+
list(params = {}, options) {
|
|
1162
|
+
return this.#http.request({
|
|
1163
|
+
method: "GET",
|
|
1164
|
+
path: "/v1/emails",
|
|
1165
|
+
query: listQuery(params),
|
|
1166
|
+
signal: options?.signal
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
829
1170
|
* Cancels a scheduled email. Canceling an already canceled email returns it; any other email
|
|
830
1171
|
* fails with `email_not_cancelable`.
|
|
831
1172
|
*/
|
|
@@ -867,6 +1208,24 @@ var EmailResource = class {
|
|
|
867
1208
|
}
|
|
868
1209
|
};
|
|
869
1210
|
//#endregion
|
|
1211
|
+
//#region src/resources/environment.ts
|
|
1212
|
+
/** `coss.environment`: which environment, project and organization the API key belongs to. */
|
|
1213
|
+
var EnvironmentResource = class {
|
|
1214
|
+
#http;
|
|
1215
|
+
/** @internal Use `new Coss().environment`. */
|
|
1216
|
+
constructor(http) {
|
|
1217
|
+
this.#http = http;
|
|
1218
|
+
}
|
|
1219
|
+
/** The API key's environment, its project and organization, and the key itself. */
|
|
1220
|
+
get(options) {
|
|
1221
|
+
return this.#http.request({
|
|
1222
|
+
method: "GET",
|
|
1223
|
+
path: "/v1/environment",
|
|
1224
|
+
signal: options?.signal
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
//#endregion
|
|
870
1229
|
//#region src/resources/hooks.ts
|
|
871
1230
|
const segment = encodeURIComponent;
|
|
872
1231
|
const applicationPath = (application) => `/v1/hooks/applications/${segment(application)}`;
|
|
@@ -1080,7 +1439,7 @@ var HookEndpointsResource = class {
|
|
|
1080
1439
|
});
|
|
1081
1440
|
}
|
|
1082
1441
|
};
|
|
1083
|
-
/** `coss.hooks.messages`: send a message once;
|
|
1442
|
+
/** `coss.hooks.messages`: send a message once; coss.com delivers it to every subscribed endpoint. */
|
|
1084
1443
|
var HookMessagesResource = class {
|
|
1085
1444
|
#http;
|
|
1086
1445
|
/** @internal Use `new Coss().hooks.messages`. */
|
|
@@ -1183,7 +1542,7 @@ var HookPortalResource = class {
|
|
|
1183
1542
|
};
|
|
1184
1543
|
/**
|
|
1185
1544
|
* `coss.hooks`: webhooks as a service. Send a message to one of your applications once
|
|
1186
|
-
* (`coss.hooks.messages.send`);
|
|
1545
|
+
* (`coss.hooks.messages.send`); coss.com delivers it to every endpoint its users registered, signed
|
|
1187
1546
|
* (Standard Webhooks), with retries, and gives them a portal to manage it all.
|
|
1188
1547
|
*/
|
|
1189
1548
|
var HooksResource = class {
|
|
@@ -1279,7 +1638,7 @@ var LogDrainsResource = class {
|
|
|
1279
1638
|
}
|
|
1280
1639
|
/**
|
|
1281
1640
|
* Changes the URL, description, sources or `enabled` flag. The signing secret stays the same.
|
|
1282
|
-
* Disabling drops the lines still waiting for the drain; enabling it again (also after
|
|
1641
|
+
* Disabling drops the lines still waiting for the drain; enabling it again (also after coss.com
|
|
1283
1642
|
* disabled it for failing) sends lines logged from then on.
|
|
1284
1643
|
*/
|
|
1285
1644
|
update(id, params, options) {
|
|
@@ -1335,7 +1694,7 @@ var LogsResource = class {
|
|
|
1335
1694
|
//#endregion
|
|
1336
1695
|
//#region src/resources/reputation.ts
|
|
1337
1696
|
/**
|
|
1338
|
-
* `coss.reputation`: the organization's standing with the
|
|
1697
|
+
* `coss.reputation`: the organization's standing with the coss.com reputation guard, which suspends
|
|
1339
1698
|
* live sending automatically when bounce or complaint rates cross its thresholds.
|
|
1340
1699
|
*/
|
|
1341
1700
|
var ReputationResource = class {
|
|
@@ -1521,7 +1880,58 @@ var RiskRulesResource = class {
|
|
|
1521
1880
|
});
|
|
1522
1881
|
}
|
|
1523
1882
|
};
|
|
1524
|
-
/**
|
|
1883
|
+
/**
|
|
1884
|
+
* `coss.risk.limitBuckets`: your rate limit buckets, which `coss.risk.limit()` charges per key.
|
|
1885
|
+
*/
|
|
1886
|
+
var RiskLimitBucketsResource = class {
|
|
1887
|
+
#http;
|
|
1888
|
+
/** @internal Use `new Coss().risk.limitBuckets`. */
|
|
1889
|
+
constructor(http) {
|
|
1890
|
+
this.#http = http;
|
|
1891
|
+
}
|
|
1892
|
+
/** Creates a bucket. Not retried automatically: a retry would find its name taken. */
|
|
1893
|
+
create(params, options) {
|
|
1894
|
+
return this.#http.request({
|
|
1895
|
+
method: "POST",
|
|
1896
|
+
path: "/v1/risk/limit_buckets",
|
|
1897
|
+
body: params,
|
|
1898
|
+
retry: false,
|
|
1899
|
+
signal: options?.signal
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
/** Every bucket, by name. */
|
|
1903
|
+
list(options) {
|
|
1904
|
+
return this.#http.request({
|
|
1905
|
+
method: "GET",
|
|
1906
|
+
path: "/v1/risk/limit_buckets",
|
|
1907
|
+
signal: options?.signal
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
get(id, options) {
|
|
1911
|
+
return this.#http.request({
|
|
1912
|
+
method: "GET",
|
|
1913
|
+
path: `/v1/risk/limit_buckets/${encodeURIComponent(id)}`,
|
|
1914
|
+
signal: options?.signal
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
/** Changes the fields given; the next check uses them. */
|
|
1918
|
+
update(id, params, options) {
|
|
1919
|
+
return this.#http.request({
|
|
1920
|
+
method: "PATCH",
|
|
1921
|
+
path: `/v1/risk/limit_buckets/${encodeURIComponent(id)}`,
|
|
1922
|
+
body: params,
|
|
1923
|
+
signal: options?.signal
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
delete(id, options) {
|
|
1927
|
+
return this.#http.request({
|
|
1928
|
+
method: "DELETE",
|
|
1929
|
+
path: `/v1/risk/limit_buckets/${encodeURIComponent(id)}`,
|
|
1930
|
+
signal: options?.signal
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
/** `coss.risk.users`: what coss.com remembers about your users. */
|
|
1525
1935
|
var RiskUsersResource = class {
|
|
1526
1936
|
#http;
|
|
1527
1937
|
/** @internal Use `new Coss().risk.users`. */
|
|
@@ -1529,7 +1939,7 @@ var RiskUsersResource = class {
|
|
|
1529
1939
|
this.#http = http;
|
|
1530
1940
|
}
|
|
1531
1941
|
/**
|
|
1532
|
-
* Call when you delete a user:
|
|
1942
|
+
* Call when you delete a user: coss.com deletes what identifies them (their mailbox, fraud reports,
|
|
1533
1943
|
* and the user id, email, IP and user agent on their assessments). Safe to repeat.
|
|
1534
1944
|
*/
|
|
1535
1945
|
forget(userId, options) {
|
|
@@ -1560,6 +1970,8 @@ var RiskResource = class {
|
|
|
1560
1970
|
settings;
|
|
1561
1971
|
/** Rules (`create`, `list`, `get`, `update`, `delete`). */
|
|
1562
1972
|
rules;
|
|
1973
|
+
/** Your rate limit buckets (`create`, `list`, `get`, `update`, `delete`). */
|
|
1974
|
+
limitBuckets;
|
|
1563
1975
|
#http;
|
|
1564
1976
|
/** @internal Use `new Coss().risk`. */
|
|
1565
1977
|
constructor(http) {
|
|
@@ -1569,6 +1981,7 @@ var RiskResource = class {
|
|
|
1569
1981
|
this.users = new RiskUsersResource(http);
|
|
1570
1982
|
this.settings = new RiskSettingsResource(http);
|
|
1571
1983
|
this.rules = new RiskRulesResource(http);
|
|
1984
|
+
this.limitBuckets = new RiskLimitBucketsResource(http);
|
|
1572
1985
|
}
|
|
1573
1986
|
/**
|
|
1574
1987
|
* Assesses an event and returns the decision with its score and reasons. Retries reuse one
|
|
@@ -1585,7 +1998,8 @@ var RiskResource = class {
|
|
|
1585
1998
|
}
|
|
1586
1999
|
/**
|
|
1587
2000
|
* Reports what an assessed user turned out to be: `fraud` and `abuse` teach later assessments to
|
|
1588
|
-
* flag the same mailbox, user, IP and subnet; `legitimate` takes that back.
|
|
2001
|
+
* flag the same mailbox, user, card, device, IP and subnet; `legitimate` takes that back.
|
|
2002
|
+
* Resolves a review, and counts in `calibration()`.
|
|
1589
2003
|
*/
|
|
1590
2004
|
feedback(id, params, options) {
|
|
1591
2005
|
return this.#http.request({
|
|
@@ -1596,6 +2010,71 @@ var RiskResource = class {
|
|
|
1596
2010
|
signal: options?.signal
|
|
1597
2011
|
});
|
|
1598
2012
|
}
|
|
2013
|
+
/**
|
|
2014
|
+
* How often each reason and rule was right over the last 90 days, by your feedback, and the
|
|
2015
|
+
* reason weights that supports. Apply them with `settings.update({ reason_weights })`.
|
|
2016
|
+
*/
|
|
2017
|
+
calibration(options) {
|
|
2018
|
+
return this.#http.request({
|
|
2019
|
+
method: "GET",
|
|
2020
|
+
path: "/v1/risk/calibration",
|
|
2021
|
+
signal: options?.signal
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Checks an email address on its own: syntax, disposable domains, deliverability, role
|
|
2026
|
+
* accounts, free providers, the canonical mailbox, typos and domain age. Nothing is stored.
|
|
2027
|
+
*/
|
|
2028
|
+
checkEmail(email, options) {
|
|
2029
|
+
return this.#http.request({
|
|
2030
|
+
method: "POST",
|
|
2031
|
+
path: "/v1/risk/email/check",
|
|
2032
|
+
body: { email },
|
|
2033
|
+
signal: options?.signal
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Checks an IP address on its own: Tor, cloud, hosting and VPN networks, ASN and country, and
|
|
2038
|
+
* VPN, proxy and relay exits when coss.com's privacy data is available.
|
|
2039
|
+
*/
|
|
2040
|
+
checkIp(ip, options) {
|
|
2041
|
+
return this.#http.request({
|
|
2042
|
+
method: "POST",
|
|
2043
|
+
path: "/v1/risk/ip/check",
|
|
2044
|
+
body: { ip },
|
|
2045
|
+
signal: options?.signal
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Scores text for spam, phishing, malware and fraud. Scores only, never a verdict: you choose
|
|
2050
|
+
* the thresholds. The text is neither stored nor logged.
|
|
2051
|
+
*/
|
|
2052
|
+
classify(params, options) {
|
|
2053
|
+
return this.#http.request({
|
|
2054
|
+
method: "POST",
|
|
2055
|
+
path: "/v1/risk/content/classify",
|
|
2056
|
+
body: params,
|
|
2057
|
+
signal: options?.signal
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Takes `cost` tokens (default 1) from `key`'s share of one of your buckets
|
|
2062
|
+
* (`coss.risk.limitBuckets`). Retries reuse one `Idempotency-Key`, so they are charged once.
|
|
2063
|
+
*
|
|
2064
|
+
* ```ts
|
|
2065
|
+
* const { data } = await coss.risk.limit({ bucket: "password_reset", key: email });
|
|
2066
|
+
* if (data && !data.allowed) return tooManyRequests(data.retry_after_ms);
|
|
2067
|
+
* ```
|
|
2068
|
+
*/
|
|
2069
|
+
limit(params, options) {
|
|
2070
|
+
return this.#http.request({
|
|
2071
|
+
method: "POST",
|
|
2072
|
+
path: "/v1/risk/limits",
|
|
2073
|
+
body: params,
|
|
2074
|
+
idempotencyKey: options?.idempotencyKey,
|
|
2075
|
+
signal: options?.signal
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
1599
2078
|
};
|
|
1600
2079
|
//#endregion
|
|
1601
2080
|
//#region src/resources/webhook-deliveries.ts
|
|
@@ -1744,6 +2223,51 @@ var WebhookEndpointsResource = class {
|
|
|
1744
2223
|
}
|
|
1745
2224
|
};
|
|
1746
2225
|
//#endregion
|
|
2226
|
+
//#region src/resources/webhook-listeners.ts
|
|
2227
|
+
/**
|
|
2228
|
+
* `coss.webhookListeners`: what `coss listen` uses to forward an environment's events to a local
|
|
2229
|
+
* server. A listener receives events created after it opens, while it is polled; it is closed by
|
|
2230
|
+
* `delete` or after 10 minutes without a poll.
|
|
2231
|
+
*/
|
|
2232
|
+
var WebhookListenersResource = class {
|
|
2233
|
+
#http;
|
|
2234
|
+
/** @internal Use `new Coss().webhookListeners`. */
|
|
2235
|
+
constructor(http) {
|
|
2236
|
+
this.#http = http;
|
|
2237
|
+
}
|
|
2238
|
+
/** Opens a listener. Never retried automatically: a lost answer leaves one to expire. */
|
|
2239
|
+
create(params = {}, options) {
|
|
2240
|
+
return this.#http.request({
|
|
2241
|
+
method: "POST",
|
|
2242
|
+
path: "/v1/webhook_listeners",
|
|
2243
|
+
retry: false,
|
|
2244
|
+
body: params,
|
|
2245
|
+
signal: options?.signal
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Acknowledges earlier deliveries and waits (up to `wait_ms`) for more. Never retried
|
|
2250
|
+
* automatically: call it in a loop, and keep unacknowledged acks for the next call after an error.
|
|
2251
|
+
*/
|
|
2252
|
+
poll(id, params = {}, options) {
|
|
2253
|
+
return this.#http.request({
|
|
2254
|
+
method: "POST",
|
|
2255
|
+
path: `/v1/webhook_listeners/${encodeURIComponent(id)}/poll`,
|
|
2256
|
+
retry: false,
|
|
2257
|
+
body: params,
|
|
2258
|
+
signal: options?.signal
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
/** Closes a listener: it receives nothing more. */
|
|
2262
|
+
delete(id, options) {
|
|
2263
|
+
return this.#http.request({
|
|
2264
|
+
method: "DELETE",
|
|
2265
|
+
path: `/v1/webhook_listeners/${encodeURIComponent(id)}`,
|
|
2266
|
+
signal: options?.signal
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
//#endregion
|
|
1747
2271
|
//#region src/client.ts
|
|
1748
2272
|
const DEFAULT_BASE_URL = "https://api.coss.com";
|
|
1749
2273
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -1764,7 +2288,7 @@ function timeoutMs(value) {
|
|
|
1764
2288
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.min(Math.ceil(value), MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
1765
2289
|
}
|
|
1766
2290
|
/**
|
|
1767
|
-
* The
|
|
2291
|
+
* The coss.com client. One API key covers every product.
|
|
1768
2292
|
*
|
|
1769
2293
|
* ```ts
|
|
1770
2294
|
* const coss = new Coss({ apiKey: "coss_test_…" });
|
|
@@ -1773,17 +2297,23 @@ function timeoutMs(value) {
|
|
|
1773
2297
|
*/
|
|
1774
2298
|
var Coss = class {
|
|
1775
2299
|
email;
|
|
2300
|
+
/** The API key's environment, project and organization (`coss.environment.get()`). */
|
|
2301
|
+
environment;
|
|
2302
|
+
/** The client's own API key (`coss.apiKey.revoke()`). */
|
|
2303
|
+
apiKey;
|
|
1776
2304
|
/** Sending domains of the organization (`coss.domains.create`, `.verify`, …). */
|
|
1777
2305
|
domains;
|
|
1778
2306
|
/** Webhook endpoints of the API key's environment. */
|
|
1779
2307
|
webhookEndpoints;
|
|
2308
|
+
/** Temporary listeners `coss listen` pulls the environment's events from. */
|
|
2309
|
+
webhookListeners;
|
|
1780
2310
|
/** The organization's sending reputation (`coss.reputation.get()`). */
|
|
1781
2311
|
reputation;
|
|
1782
2312
|
/** The environment's log: API requests, email events, webhook attempts (`coss.logs.list()`). */
|
|
1783
2313
|
logs;
|
|
1784
2314
|
/** HTTPS endpoints the environment's log is streamed to (`coss.logDrains.create()`). */
|
|
1785
2315
|
logDrains;
|
|
1786
|
-
/** The organization's audit log: changes by members, API keys and
|
|
2316
|
+
/** The organization's audit log: changes by members, API keys and coss.com support (`coss.auditEvents.list()`). */
|
|
1787
2317
|
auditEvents;
|
|
1788
2318
|
/** Hooks, webhooks as a service: `coss.hooks.messages.send()` delivers to your users' endpoints. */
|
|
1789
2319
|
hooks;
|
|
@@ -1796,9 +2326,9 @@ var Coss = class {
|
|
|
1796
2326
|
* `dangerouslyAllowBrowser`. Request failures are returned as `{ data: null, error }`.
|
|
1797
2327
|
*/
|
|
1798
2328
|
constructor(options = {}) {
|
|
1799
|
-
if (typeof globalThis.document !== "undefined" && !options.dangerouslyAllowBrowser) throw new Error("Refusing to create a
|
|
2329
|
+
if (typeof globalThis.document !== "undefined" && !options.dangerouslyAllowBrowser) throw new Error("Refusing to create a coss.com client in a browser-like environment: coss.com API keys are secret, and any key sent to the browser can be read by anyone using the page. Call coss.com from your server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk.");
|
|
1800
2330
|
const apiKey = options.apiKey?.trim() || readEnv("COSS_API_KEY");
|
|
1801
|
-
if (!apiKey) throw new Error("Missing
|
|
2331
|
+
if (!apiKey) throw new Error("Missing coss.com API key. Pass `apiKey` to `new Coss({ apiKey })` or set the COSS_API_KEY environment variable.");
|
|
1802
2332
|
const http = new HttpClient({
|
|
1803
2333
|
apiKey,
|
|
1804
2334
|
baseUrl: options.baseUrl?.trim() || readEnv("COSS_BASE_URL") || DEFAULT_BASE_URL,
|
|
@@ -1808,8 +2338,11 @@ var Coss = class {
|
|
|
1808
2338
|
apiVersion: options.apiVersion?.trim() || "2026-10-10"
|
|
1809
2339
|
});
|
|
1810
2340
|
this.email = new EmailResource(http);
|
|
2341
|
+
this.environment = new EnvironmentResource(http);
|
|
2342
|
+
this.apiKey = new ApiKeyResource(http);
|
|
1811
2343
|
this.domains = new DomainsResource(http);
|
|
1812
2344
|
this.webhookEndpoints = new WebhookEndpointsResource(http);
|
|
2345
|
+
this.webhookListeners = new WebhookListenersResource(http);
|
|
1813
2346
|
this.reputation = new ReputationResource(http);
|
|
1814
2347
|
this.logs = new LogsResource(http);
|
|
1815
2348
|
this.logDrains = new LogDrainsResource(http);
|
|
@@ -1883,7 +2416,7 @@ async function verifySignature(payload, headers, secret, options) {
|
|
|
1883
2416
|
* const event = await verifyWebhook(await request.text(), request.headers, process.env.COSS_WEBHOOK_SECRET);
|
|
1884
2417
|
* ```
|
|
1885
2418
|
*
|
|
1886
|
-
* The same check verifies messages your own users receive from
|
|
2419
|
+
* The same check verifies messages your own users receive from coss.com Hooks (`coss.hooks`): pass
|
|
1887
2420
|
* the endpoint's secret and name your payload type, e.g. `verifyWebhook<InvoicePaid>(…)`.
|
|
1888
2421
|
*/
|
|
1889
2422
|
async function verifyWebhook(payload, headers, secret, options = {}) {
|