@sendora/sdk 1.1.0 → 2.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.
Files changed (69) hide show
  1. package/README.md +152 -70
  2. package/dist/account.d.ts +30 -0
  3. package/dist/account.d.ts.map +1 -0
  4. package/dist/account.js +25 -0
  5. package/dist/account.js.map +1 -0
  6. package/dist/client.d.ts +13 -16
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +11 -30
  9. package/dist/client.js.map +1 -1
  10. package/dist/domains.d.ts +1 -1
  11. package/dist/domains.d.ts.map +1 -1
  12. package/dist/domains.js +1 -1
  13. package/dist/domains.js.map +1 -1
  14. package/dist/error.d.ts +6 -3
  15. package/dist/error.d.ts.map +1 -1
  16. package/dist/error.js +22 -2
  17. package/dist/error.js.map +1 -1
  18. package/dist/inbound-domains.d.ts +36 -0
  19. package/dist/inbound-domains.d.ts.map +1 -0
  20. package/dist/inbound-domains.js +72 -0
  21. package/dist/inbound-domains.js.map +1 -0
  22. package/dist/inbound.d.ts +61 -0
  23. package/dist/inbound.d.ts.map +1 -0
  24. package/dist/inbound.js +106 -0
  25. package/dist/inbound.js.map +1 -0
  26. package/dist/index.d.ts +8 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/options.d.ts +20 -0
  31. package/dist/options.d.ts.map +1 -0
  32. package/dist/options.js +34 -0
  33. package/dist/options.js.map +1 -0
  34. package/dist/servers.d.ts +64 -0
  35. package/dist/servers.d.ts.map +1 -0
  36. package/dist/servers.js +133 -0
  37. package/dist/servers.js.map +1 -0
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.d.ts.map +1 -1
  40. package/dist/transport.js +15 -5
  41. package/dist/transport.js.map +1 -1
  42. package/dist/types.d.ts +182 -5
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist/webhook-verify.d.ts +7 -5
  47. package/dist/webhook-verify.d.ts.map +1 -1
  48. package/dist/webhook-verify.js +38 -17
  49. package/dist/webhook-verify.js.map +1 -1
  50. package/dist/webhooks.d.ts +13 -1
  51. package/dist/webhooks.d.ts.map +1 -1
  52. package/dist/webhooks.js +26 -0
  53. package/dist/webhooks.js.map +1 -1
  54. package/package.json +4 -9
  55. package/skills/sendora/SKILL.md +19 -9
  56. package/src/account.ts +40 -0
  57. package/src/client.ts +20 -43
  58. package/src/domains.ts +1 -1
  59. package/src/error.ts +36 -3
  60. package/src/inbound-domains.ts +89 -0
  61. package/src/inbound.ts +135 -0
  62. package/src/index.ts +8 -1
  63. package/src/options.ts +51 -0
  64. package/src/servers.ts +166 -0
  65. package/src/transport.ts +26 -5
  66. package/src/types.ts +194 -5
  67. package/src/version.ts +1 -1
  68. package/src/webhook-verify.ts +39 -18
  69. package/src/webhooks.ts +29 -0
@@ -15,11 +15,13 @@ const encoder = new TextEncoder();
15
15
  const decoder = new TextDecoder();
16
16
  /**
17
17
  * Checks a webhook request came from Sendora and answers its event, typed
18
- * by `event`. The header is `Sendora-Signature: t=<unix seconds>,v1=<hex>`,
19
- * the hex an HMAC-SHA256 with your secret over `<t>.<raw body>`; the
20
- * timestamp is inside the signed text, so a captured request cannot be
21
- * replayed after the tolerance. Throws `SendoraError` with
22
- * `invalid_signature` or `stale_signature`.
18
+ * by `event`. The header is `Sendora-Signature: t=<unix seconds>,v1=<hex>[,v1=<hex>]`,
19
+ * one hex per live secret of the webhook, each an HMAC-SHA256 with that
20
+ * secret over `<t>.<raw body>`; the one your secret produces is enough, so
21
+ * a secret being rolled in verifies as well as the old one. The timestamp
22
+ * is inside the signed text, so a captured request cannot be replayed
23
+ * after the tolerance. Throws `SendoraError` with `invalid_signature` or
24
+ * `stale_signature`.
23
25
  *
24
26
  * @example
25
27
  * const event = await verifyWebhook({
@@ -48,32 +50,51 @@ export async function verifyWebhook(input) {
48
50
  const body = typeof input.body === 'string' ? encoder.encode(input.body) : input.body;
49
51
  const signed = concat(encoder.encode(`${String(signature.timestamp)}.`), body);
50
52
  const key = await crypto.subtle.importKey('raw', encoder.encode(input.secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
51
- if (!(await crypto.subtle.verify('HMAC', key, signature.mac, signed))) {
52
- throw invalidSignature('The signature does not match the body.');
53
+ // Every signature is checked, so the time taken says nothing about which one matched.
54
+ let matched = false;
55
+ for (const mac of signature.macs) {
56
+ matched = (await crypto.subtle.verify('HMAC', key, mac, signed)) || matched;
57
+ }
58
+ if (!matched) {
59
+ throw invalidSignature('No signature in the header matches the body under this secret.');
53
60
  }
54
61
  return parseEvent(decoder.decode(body));
55
62
  }
63
+ /** The timestamp and every `v1` value, one per live secret of the webhook. */
56
64
  function parseSignature(header) {
57
65
  if (typeof header !== 'string' || header === '') {
58
66
  throw invalidSignature('The request carries no Sendora-Signature header.');
59
67
  }
60
- const parts = new Map();
68
+ let timestamp;
69
+ const macs = [];
61
70
  for (const part of header.split(',')) {
62
71
  const separator = part.indexOf('=');
63
- if (separator > 0) {
64
- parts.set(part.slice(0, separator).trim(), part.slice(separator + 1).trim());
72
+ if (separator <= 0) {
73
+ continue;
74
+ }
75
+ const name = part.slice(0, separator).trim();
76
+ const value = part.slice(separator + 1).trim();
77
+ if (name === 't') {
78
+ timestamp = Number(value);
79
+ }
80
+ else if (name === 'v1') {
81
+ if (!MAC_HEX.test(value)) {
82
+ throw invalidSignature('The Sendora-Signature header is malformed.');
83
+ }
84
+ macs.push(bytesOf(value));
65
85
  }
66
86
  }
67
- const timestamp = Number(parts.get('t'));
68
- const hex = parts.get('v1');
69
- if (!Number.isInteger(timestamp) || hex === undefined || !MAC_HEX.test(hex)) {
87
+ if (timestamp === undefined || !Number.isInteger(timestamp) || macs.length === 0) {
70
88
  throw invalidSignature('The Sendora-Signature header is malformed.');
71
89
  }
72
- const mac = new Uint8Array(hex.length / 2);
73
- for (let i = 0; i < mac.length; i += 1) {
74
- mac[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
90
+ return { timestamp, macs };
91
+ }
92
+ function bytesOf(hex) {
93
+ const bytes = new Uint8Array(hex.length / 2);
94
+ for (let i = 0; i < bytes.length; i += 1) {
95
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
75
96
  }
76
- return { timestamp, mac };
97
+ return bytes;
77
98
  }
78
99
  function parseEvent(text) {
79
100
  let value;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-verify.js","sourceRoot":"","sources":["../src/webhook-verify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webhook-verify.js","sourceRoot":"","sources":["../src/webhook-verify.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { Transport } from './transport.ts';
2
- import type { CreatedWebhook, CreateWebhookRequest, Delivery, DeliveryPage, DeliveryQuery, ReceiveOptions, RequestOptions, Webhook, WebhookEvent, WebhookList } from './types.ts';
2
+ import type { CreatedWebhook, CreatedWebhookSecret, CreateWebhookRequest, Delivery, DeliveryPage, DeliveryQuery, ReceiveOptions, RequestOptions, Webhook, WebhookEvent, WebhookList } from './types.ts';
3
3
  /** Where the server's events are posted, every delivery of them, and the receiving side. */
4
4
  export declare class WebhooksResource {
5
5
  #private;
@@ -23,6 +23,18 @@ export declare class WebhooksResource {
23
23
  get(webhookId: string, options?: RequestOptions): Promise<Webhook>;
24
24
  /** Removes the webhook; deliveries still pending are dropped with it. */
25
25
  delete(webhookId: string, options?: RequestOptions): Promise<void>;
26
+ /**
27
+ * Adds a second live secret and answers its value once. Every delivery
28
+ * then carries one signature per live secret, the newest first, so
29
+ * switch your receiver to the new secret and delete the old one; a third
30
+ * is refused with `secret_limit`.
31
+ *
32
+ *
33
+ * const { secret, secretId } = await sendora.webhooks.createSecret(webhookId);
34
+ */
35
+ createSecret(webhookId: string, options?: RequestOptions): Promise<CreatedWebhookSecret>;
36
+ /** Deletes a secret; deliveries are no longer signed with it. The last live secret is refused (`last_secret`). */
37
+ deleteSecret(webhookId: string, secretId: string, options?: RequestOptions): Promise<void>;
26
38
  /**
27
39
  * One page of the events handed to the webhook, newest first, with the
28
40
  * outcome of the last attempt; `status` narrows to `pending`,
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,OAAO,EACP,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,4FAA4F;AAC5F,qBAAa,gBAAgB;;gBAGf,SAAS,EAAE,SAAS;IAIhC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,OAAO,EAAE,oBAAoB,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;IAU5F,mCAAmC;IACnC,IAAI,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IASxD,yBAAyB;IACzB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAStE,yEAAyE;IACzE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAStE;;;;;;;OAOG;IACH,UAAU,CACR,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,aAAkB,EACzB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC;IAUxB,uEAAuE;IACvE,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,aAAkB,EACzB,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAAC,QAAQ,CAAC;IAS1B;;;;;;;OAOG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAS9F;;;;;;;;;;;;;;OAcG;IACG,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC;CAQzB"}
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,OAAO,EACP,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,4FAA4F;AAC5F,qBAAa,gBAAgB;;gBAGf,SAAS,EAAE,SAAS;IAIhC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,OAAO,EAAE,oBAAoB,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;IAU5F,mCAAmC;IACnC,IAAI,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IASxD,yBAAyB;IACzB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAStE,yEAAyE;IACzE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAStE;;;;;;;;OAQG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAS5F,kHAAkH;IAClH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9F;;;;;;;OAOG;IACH,UAAU,CACR,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,aAAkB,EACzB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC;IAUxB,uEAAuE;IACvE,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,aAAkB,EACzB,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAAC,QAAQ,CAAC;IAS1B;;;;;;;OAOG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAS9F;;;;;;;;;;;;;;OAcG;IACG,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC;CAQzB"}
package/dist/webhooks.js CHANGED
@@ -54,6 +54,32 @@ export class WebhooksResource {
54
54
  signal: options.signal,
55
55
  });
56
56
  }
57
+ /**
58
+ * Adds a second live secret and answers its value once. Every delivery
59
+ * then carries one signature per live secret, the newest first, so
60
+ * switch your receiver to the new secret and delete the old one; a third
61
+ * is refused with `secret_limit`.
62
+ *
63
+ *
64
+ * const { secret, secretId } = await sendora.webhooks.createSecret(webhookId);
65
+ */
66
+ createSecret(webhookId, options = {}) {
67
+ return this.#transport.request({
68
+ method: 'POST',
69
+ path: `/v1/webhooks/${encodeURIComponent(webhookId)}/secrets`,
70
+ idempotent: false,
71
+ signal: options.signal,
72
+ });
73
+ }
74
+ /** Deletes a secret; deliveries are no longer signed with it. The last live secret is refused (`last_secret`). */
75
+ deleteSecret(webhookId, secretId, options = {}) {
76
+ return this.#transport.request({
77
+ method: 'DELETE',
78
+ path: `/v1/webhooks/${encodeURIComponent(webhookId)}/secrets/${encodeURIComponent(secretId)}`,
79
+ idempotent: true,
80
+ signal: options.signal,
81
+ });
82
+ }
57
83
  /**
58
84
  * One page of the events handed to the webhook, newest first, with the
59
85
  * outcome of the last attempt; `status` narrows to `pending`,
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sendora/sdk",
3
- "version": "1.1.0",
4
- "description": "The official TypeScript SDK for Sendora, transactional email delivery hosted in Sweden.",
3
+ "version": "2.1.0",
4
+ "description": "The official TypeScript SDK for Sendora, transactional email delivery.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -21,11 +21,6 @@
21
21
  "engines": {
22
22
  "node": ">=20.19"
23
23
  },
24
- "repository": {
25
- "type": "git",
26
- "url": "git+https://github.com/hornta/sendora.git",
27
- "directory": "sdks/typescript"
28
- },
29
24
  "homepage": "https://sendora.se/docs",
30
25
  "keywords": [
31
26
  "sendora",
@@ -38,10 +33,10 @@
38
33
  "typescript": "~6.0.3",
39
34
  "vitest": "^4.1.11",
40
35
  "zod": "^4.6.2",
36
+ "@sendora/observability": "0.0.0",
41
37
  "@sendora/api": "0.0.0",
42
- "@sendora/db": "0.0.0",
43
38
  "@sendora/domain": "0.0.0",
44
- "@sendora/observability": "0.0.0"
39
+ "@sendora/db": "0.0.0"
45
40
  },
46
41
  "scripts": {
47
42
  "build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sendora
3
- description: Send transactional email through Sendora with @sendora/sdk, read the message log, manage streams, suppressions, tokens, webhooks and sending domains, and receive signed webhooks. Use when a task mentions Sendora, @sendora/sdk, or sending email from a system hosted in Sweden.
3
+ description: Send transactional email through Sendora with @sendora/sdk, read the message log, manage streams, suppressions, keys, webhooks, servers and sending domains, and receive signed webhooks. Use when a task mentions Sendora, @sendora/sdk, or sending email from a system hosted in Sweden.
4
4
  ---
5
5
 
6
6
  # Sendora
@@ -12,12 +12,20 @@ complete reference; every call, every error code and every event is in it.
12
12
 
13
13
  ## Rules
14
14
 
15
- - The API token is a secret. Read it from the environment on the server
16
- and never send it to a browser. `new Sendora({ token })` throws when the
17
- token is missing, so construct the client at startup.
18
- - One client per server token: `const sendora = new Sendora({ token })`.
19
- Every resource hangs off it: `email`, `messages`, `streams`,
20
- `suppressions`, `tokens`, `webhooks`, `domains`.
15
+ - Two kinds of key, two clients. A server key (`sk_…`) drives
16
+ `new Sendora({ token })`: `email`, `messages`, `inbound`, `streams`,
17
+ `suppressions`, `tokens`, `webhooks`, `inboundDomains`, everything inside
18
+ one server. An account key (`ak_…`) drives `new SendoraAccount({ token })`:
19
+ `servers`, `servers.tokens` and `domains`, and it never sends. A key of
20
+ the wrong kind throws `wrong_token_kind`.
21
+ - Every key is a secret. Read it from the environment on the server and
22
+ never send it to a browser. Either client throws when its key is
23
+ missing, so construct it at startup.
24
+ - A server is created with its first key: `account.servers.create({ name })`
25
+ answers `token.token` once, and `new Sendora({ token })` sends with it. A
26
+ server or the account holds at most two live keys, so rotate by creating
27
+ the new key, switching to it, then revoking the old one; the last live
28
+ key can never be revoked.
21
29
  - Every method takes what the route takes and answers what the route
22
30
  answers. Do not build request bodies by hand or call `fetch` yourself.
23
31
  - Do not add idempotency keys or retry loops of your own. The SDK gives
@@ -40,8 +48,10 @@ complete reference; every call, every error code and every event is in it.
40
48
  `suppressions.delete({ address })`; a spam complaint cannot be lifted by
41
49
  the customer. Lists are per stream: pass `streamId` when the send went on
42
50
  a stream other than the default.
43
- - `from` must be on a verified sending domain; `domains.create` answers
44
- the two DNS records to add, and `domains.verify` checks them now.
51
+ - `from` must be on a verified sending domain of the account;
52
+ `account.domains.create` answers the two DNS records to add, and
53
+ `account.domains.verify` checks them now. A server key cannot manage
54
+ domains.
45
55
  - A message goes on the server's default transactional stream unless it
46
56
  names another with `streamId`; `streams.create` adds one. Never make a
47
57
  stream per message or per customer; a stream is a kind of mail.
package/src/account.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { DomainsResource } from './domains.ts';
2
+ import { transportFor, type ClientOptions } from './options.ts';
3
+ import { ServersResource } from './servers.ts';
4
+
5
+ export interface SendoraAccountOptions extends ClientOptions {
6
+ /**
7
+ * An account key (`ak_…`), created by an administrator in the dashboard.
8
+ * It manages the account and never sends. Keep it on the server; never
9
+ * ship it to a browser. `undefined`, as an unset environment variable
10
+ * gives, throws at construction rather than at the first call.
11
+ */
12
+ token: string | undefined;
13
+ }
14
+
15
+ /**
16
+ * The account client: the servers, each server's keys and the sending
17
+ * domains, under an account key. Sending, the log, streams, suppressions
18
+ * and webhooks belong to a server and to `Sendora` with a server key.
19
+ *
20
+ * @example
21
+ * import { Sendora, SendoraAccount } from '@sendora/sdk';
22
+ *
23
+ * const account = new SendoraAccount({ token: process.env.SENDORA_ACCOUNT_TOKEN });
24
+ * const server = await account.servers.create({ name: 'Fakturering' });
25
+ * const sendora = new Sendora({ token: server.token.token });
26
+ */
27
+ export class SendoraAccount {
28
+ readonly servers: ServersResource;
29
+ readonly domains: DomainsResource;
30
+
31
+ constructor(options: SendoraAccountOptions) {
32
+ const transport = transportFor(
33
+ options.token,
34
+ options,
35
+ 'SendoraAccount needs an account key (ak_…); an administrator creates one in the dashboard.',
36
+ );
37
+ this.servers = new ServersResource(transport);
38
+ this.domains = new DomainsResource(transport);
39
+ }
40
+ }
package/src/client.ts CHANGED
@@ -1,37 +1,28 @@
1
- import { DomainsResource } from './domains.ts';
2
1
  import { BroadcastsResource } from './broadcasts.ts';
3
2
  import { EmailResource } from './email.ts';
3
+ import { InboundResource } from './inbound.ts';
4
+ import { InboundDomainsResource } from './inbound-domains.ts';
4
5
  import { MessagesResource } from './messages.ts';
5
- import { DEFAULT_MAX_RETRIES } from './retry.ts';
6
+ import { transportFor, type ClientOptions } from './options.ts';
6
7
  import { StreamsResource } from './streams.ts';
7
8
  import { SuppressionsResource } from './suppressions.ts';
8
9
  import { TokensResource } from './tokens.ts';
9
- import { Transport } from './transport.ts';
10
- import { SDK_VERSION } from './version.ts';
11
10
  import { WebhooksResource } from './webhooks.ts';
12
11
 
13
- export const DEFAULT_BASE_URL = 'https://api.sendora.se';
14
- const DEFAULT_TIMEOUT_MS = 30_000;
15
-
16
- export interface SendoraOptions {
12
+ export interface SendoraOptions extends ClientOptions {
17
13
  /**
18
- * A server's API token, created in the dashboard. Keep it on the server;
19
- * never ship it to a browser. `undefined`, as an unset environment
20
- * variable gives, throws at construction rather than at the first call.
14
+ * A server key (`sk_…`), answered once when the server is created or a
15
+ * key is added to it. Keep it on the server; never ship it to a browser.
16
+ * `undefined`, as an unset environment variable gives, throws at
17
+ * construction rather than at the first call.
21
18
  */
22
19
  token: string | undefined;
23
- /** The API's origin; https://api.sendora.se unless you test against another. */
24
- baseUrl?: string | undefined;
25
- /** The fetch to use; the global one unless you need a proxy or a fake. */
26
- fetch?: typeof fetch | undefined;
27
- /** How long one attempt may take; 30 seconds by default. */
28
- timeoutMs?: number | undefined;
29
- /** How many times a failed call is repeated when repeating is safe; 2 by default, 0 turns retries off. */
30
- maxRetries?: number | undefined;
31
20
  }
32
21
 
33
22
  /**
34
- * The client. One instance per server token; every resource hangs off it.
23
+ * The server client. One instance per server key; every resource of the
24
+ * server hangs off it. The account's servers and sending domains are
25
+ * `SendoraAccount` with an account key.
35
26
  *
36
27
  * @example
37
28
  * import { Sendora } from '@sendora/sdk';
@@ -48,41 +39,27 @@ export class Sendora {
48
39
  readonly email: EmailResource;
49
40
  readonly broadcasts: BroadcastsResource;
50
41
  readonly messages: MessagesResource;
42
+ readonly inbound: InboundResource;
43
+ readonly inboundDomains: InboundDomainsResource;
51
44
  readonly streams: StreamsResource;
52
45
  readonly suppressions: SuppressionsResource;
53
46
  readonly tokens: TokensResource;
54
47
  readonly webhooks: WebhooksResource;
55
- readonly domains: DomainsResource;
56
48
 
57
49
  constructor(options: SendoraOptions) {
58
- if (options.token === undefined || options.token.trim() === '') {
59
- throw new TypeError('Sendora needs a server API token; create one in the dashboard.');
60
- }
61
- const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
62
- new URL(baseUrl);
63
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
64
- if (!(timeoutMs > 0)) {
65
- throw new TypeError('timeoutMs must be a positive number of milliseconds.');
66
- }
67
- const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
68
- if (!Number.isInteger(maxRetries) || maxRetries < 0) {
69
- throw new TypeError('maxRetries must be a whole number of zero or more.');
70
- }
71
- const transport = new Transport({
72
- baseUrl,
73
- token: options.token,
74
- fetch: options.fetch ?? fetch,
75
- timeoutMs,
76
- maxRetries,
77
- userAgent: `sendora-sdk/${SDK_VERSION}`,
78
- });
50
+ const transport = transportFor(
51
+ options.token,
52
+ options,
53
+ 'Sendora needs a server key (sk_…); it is shown once when the server or the key is created.',
54
+ );
79
55
  this.email = new EmailResource(transport);
80
56
  this.broadcasts = new BroadcastsResource(transport);
81
57
  this.messages = new MessagesResource(transport);
58
+ this.inbound = new InboundResource(transport);
59
+ this.inboundDomains = new InboundDomainsResource(transport);
82
60
  this.streams = new StreamsResource(transport);
83
61
  this.suppressions = new SuppressionsResource(transport);
84
62
  this.tokens = new TokensResource(transport);
85
63
  this.webhooks = new WebhooksResource(transport);
86
- this.domains = new DomainsResource(transport);
87
64
  }
88
65
  }
package/src/domains.ts CHANGED
@@ -7,7 +7,7 @@ import type {
7
7
  VerifiedDomain,
8
8
  } from './types.ts';
9
9
 
10
- /** The account's sending domains and the two DNS records each one needs. Any server's token manages them. */
10
+ /** The account's sending domains and the two DNS records each one needs, managed with the account key; every server sends from them. */
11
11
  export class DomainsResource {
12
12
  readonly #transport: Transport;
13
13
 
package/src/error.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { LimitScope, SuppressedRecipient, ValidationIssue } from './types.ts';
2
2
 
3
- /** Every code the API answers, the three the SDK raises when it never got a proper answer, and the two of the webhook verifier. */
3
+ /** Every code the API answers, under either kind of key, the three the SDK raises when it never got a proper answer, and the two of the webhook verifier. */
4
4
  export type SendoraErrorCode =
5
5
  | 'invalid_request'
6
6
  | 'unauthorized'
7
+ | 'wrong_token_kind'
7
8
  | 'payment_required'
8
9
  | 'tenant_paused'
9
10
  | 'tenant_not_active'
@@ -11,11 +12,20 @@ export type SendoraErrorCode =
11
12
  | 'unsubscribe_locked'
12
13
  | 'not_found'
13
14
  | 'domain_exists'
15
+ | 'domain_reserved'
16
+ | 'inbound_domain_exists'
14
17
  | 'webhook_exists'
15
18
  | 'stream_exists'
16
19
  | 'inbound_stream_exists'
17
20
  | 'default_stream'
18
21
  | 'last_token'
22
+ | 'token_limit'
23
+ | 'last_secret'
24
+ | 'secret_limit'
25
+ | 'server_exists'
26
+ | 'server_limit'
27
+ | 'server_reserved'
28
+ | 'server_in_flight'
19
29
  | 'request_too_large'
20
30
  | 'from_domain_not_verified'
21
31
  | 'stream_not_found'
@@ -28,6 +38,8 @@ export type SendoraErrorCode =
28
38
  | 'substitution_missing'
29
39
  | 'unsubscribe_placeholder_missing'
30
40
  | 'list_unsubscribe_reserved'
41
+ | 'content_expired'
42
+ | 'content_unreadable'
31
43
  | 'recipient_suppressed'
32
44
  | 'idempotency_key_mismatch'
33
45
  | 'idempotency_key_required'
@@ -43,6 +55,7 @@ export type SendoraErrorCode =
43
55
  const apiCodes: ReadonlySet<string> = new Set<SendoraErrorCode>([
44
56
  'invalid_request',
45
57
  'unauthorized',
58
+ 'wrong_token_kind',
46
59
  'payment_required',
47
60
  'tenant_paused',
48
61
  'tenant_not_active',
@@ -50,11 +63,20 @@ const apiCodes: ReadonlySet<string> = new Set<SendoraErrorCode>([
50
63
  'unsubscribe_locked',
51
64
  'not_found',
52
65
  'domain_exists',
66
+ 'domain_reserved',
67
+ 'inbound_domain_exists',
53
68
  'webhook_exists',
54
69
  'stream_exists',
55
70
  'inbound_stream_exists',
56
71
  'default_stream',
57
72
  'last_token',
73
+ 'token_limit',
74
+ 'last_secret',
75
+ 'secret_limit',
76
+ 'server_exists',
77
+ 'server_limit',
78
+ 'server_reserved',
79
+ 'server_in_flight',
58
80
  'request_too_large',
59
81
  'from_domain_not_verified',
60
82
  'stream_not_found',
@@ -67,6 +89,8 @@ const apiCodes: ReadonlySet<string> = new Set<SendoraErrorCode>([
67
89
  'substitution_missing',
68
90
  'unsubscribe_placeholder_missing',
69
91
  'list_unsubscribe_reserved',
92
+ 'content_expired',
93
+ 'content_unreadable',
70
94
  'recipient_suppressed',
71
95
  'idempotency_key_mismatch',
72
96
  'idempotency_key_required',
@@ -85,6 +109,7 @@ export interface SendoraErrorFields {
85
109
  cap?: number | null;
86
110
  used?: number | null;
87
111
  resetsAt?: string | null;
112
+ max?: number | null;
88
113
  issues?: ValidationIssue[];
89
114
  suppressed?: SuppressedRecipient[];
90
115
  streamId?: string | null;
@@ -124,13 +149,15 @@ export class SendoraError extends Error {
124
149
  readonly used: number | null;
125
150
  /** When the month counter resets, on monthly_cap_reached. */
126
151
  readonly resetsAt: string | null;
152
+ /** Live keys the server or the account may hold, on token_limit; servers the account may have, on server_limit; live secrets a webhook may hold, on secret_limit. */
153
+ readonly max: number | null;
127
154
  /** One entry per invalid field, on invalid_request. */
128
155
  readonly issues: ValidationIssue[];
129
156
  /** The recipients the server refused, on recipient_suppressed. */
130
157
  readonly suppressed: SuppressedRecipient[];
131
158
  /** The stream whose suppression list refused the send, on recipient_suppressed. */
132
159
  readonly streamId: string | null;
133
- /** The id of the domain, webhook or stream that already exists, on domain_exists, webhook_exists and stream_exists. */
160
+ /** The id of the domain, webhook or stream that already exists, on domain_exists, webhook_exists, stream_exists, inbound_stream_exists and, when the stream already has a domain, inbound_domain_exists. */
134
161
  readonly existingId: string | null;
135
162
 
136
163
  constructor(fields: SendoraErrorFields) {
@@ -143,6 +170,7 @@ export class SendoraError extends Error {
143
170
  this.cap = fields.cap ?? null;
144
171
  this.used = fields.used ?? null;
145
172
  this.resetsAt = fields.resetsAt ?? null;
173
+ this.max = fields.max ?? null;
146
174
  this.issues = fields.issues ?? [];
147
175
  this.suppressed = fields.suppressed ?? [];
148
176
  this.streamId = fields.streamId ?? null;
@@ -172,6 +200,7 @@ export class SendoraError extends Error {
172
200
  cap: this.cap,
173
201
  used: this.used,
174
202
  resetsAt: this.resetsAt,
203
+ max: this.max,
175
204
  issues: this.issues,
176
205
  suppressed: this.suppressed,
177
206
  streamId: this.streamId,
@@ -205,11 +234,15 @@ export function errorFromAnswer(
205
234
  cap: integerOf(answer.cap),
206
235
  used: integerOf(answer.used),
207
236
  resetsAt: typeof answer.resetsAt === 'string' ? answer.resetsAt : null,
237
+ max: integerOf(answer.max) ?? integerOf(answer.maxServers),
208
238
  issues,
209
239
  suppressed: Array.isArray(answer.suppressed) ? answer.suppressed.filter(isSuppressed) : [],
210
240
  streamId: code === 'recipient_suppressed' ? stringOf(answer.streamId) : null,
211
241
  existingId:
212
- stringOf(answer.domainId) ?? stringOf(answer.webhookId) ?? stringOf(answer.streamId),
242
+ stringOf(answer.domainId) ??
243
+ stringOf(answer.webhookId) ??
244
+ stringOf(answer.streamId) ??
245
+ stringOf(answer.inboundDomainId),
213
246
  });
214
247
  }
215
248
 
@@ -0,0 +1,89 @@
1
+ import type { Transport } from './transport.ts';
2
+ import type {
3
+ CreateInboundDomainRequest,
4
+ InboundDomain,
5
+ InboundDomainList,
6
+ RequestOptions,
7
+ VerifiedInboundDomain,
8
+ } from './types.ts';
9
+
10
+ /** The server's own domains for receiving: one per inbound stream, catch-all, with the two DNS records each one needs. */
11
+ export class InboundDomainsResource {
12
+ readonly #transport: Transport;
13
+
14
+ constructor(transport: Transport) {
15
+ this.#transport = transport;
16
+ }
17
+
18
+ /**
19
+ * Claims a domain for an inbound stream and answers the two records to
20
+ * create: an MX that brings the domain's mail to Sendora and a TXT
21
+ * record that proves the claim. Mail is accepted once both are seen.
22
+ * `domain_reserved` is a name of Sendora's own; `inbound_domain_exists`
23
+ * is a stream that already has a domain (`existingId` names it) or a
24
+ * name another account holds verified.
25
+ *
26
+ * @example
27
+ * const domain = await sendora.inboundDomains.create({ streamId, domain: 'post.example.se' });
28
+ * console.log(domain.mx.host, domain.mx.value);
29
+ * console.log(domain.txt.host, domain.txt.value);
30
+ */
31
+ create(
32
+ request: CreateInboundDomainRequest,
33
+ options: RequestOptions = {},
34
+ ): Promise<InboundDomain> {
35
+ return this.#transport.request<InboundDomain>({
36
+ method: 'POST',
37
+ path: '/v1/inbound/domains',
38
+ body: request,
39
+ idempotent: false,
40
+ signal: options.signal,
41
+ });
42
+ }
43
+
44
+ /** Every inbound domain of the token's server. */
45
+ list(options: RequestOptions = {}): Promise<InboundDomainList> {
46
+ return this.#transport.request<InboundDomainList>({
47
+ method: 'GET',
48
+ path: '/v1/inbound/domains',
49
+ idempotent: true,
50
+ signal: options.signal,
51
+ });
52
+ }
53
+
54
+ /** One domain by id, with the state of its records. */
55
+ get(inboundDomainId: string, options: RequestOptions = {}): Promise<InboundDomain> {
56
+ return this.#transport.request<InboundDomain>({
57
+ method: 'GET',
58
+ path: `/v1/inbound/domains/${encodeURIComponent(inboundDomainId)}`,
59
+ idempotent: true,
60
+ signal: options.signal,
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Looks both records up now and answers the domain as it stands plus
66
+ * what each lookup found: `ok`, `missing`, `mismatch` or `dns_error`.
67
+ *
68
+ * @example
69
+ * const { verified, check } = await sendora.inboundDomains.verify(inboundDomainId);
70
+ */
71
+ verify(inboundDomainId: string, options: RequestOptions = {}): Promise<VerifiedInboundDomain> {
72
+ return this.#transport.request<VerifiedInboundDomain>({
73
+ method: 'POST',
74
+ path: `/v1/inbound/domains/${encodeURIComponent(inboundDomainId)}/verify`,
75
+ idempotent: true,
76
+ signal: options.signal,
77
+ });
78
+ }
79
+
80
+ /** Removes the domain; mail to it is refused from then on, and the name is free for another claim. */
81
+ delete(inboundDomainId: string, options: RequestOptions = {}): Promise<void> {
82
+ return this.#transport.request<undefined>({
83
+ method: 'DELETE',
84
+ path: `/v1/inbound/domains/${encodeURIComponent(inboundDomainId)}`,
85
+ idempotent: true,
86
+ signal: options.signal,
87
+ });
88
+ }
89
+ }