@posthaste/sdk 0.1.0 → 0.3.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 +182 -31
- package/dist/base64.d.ts +10 -0
- package/dist/base64.js +22 -0
- package/dist/client.d.ts +102 -11
- package/dist/client.js +181 -14
- package/dist/errors.d.ts +46 -4
- package/dist/errors.js +40 -4
- package/dist/http.d.ts +41 -5
- package/dist/http.js +71 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/pagination.d.ts +66 -24
- package/dist/pagination.js +58 -26
- package/dist/types.d.ts +224 -20
- package/dist/types.js +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,91 @@ await posthaste.emails.send({
|
|
|
89
89
|
});
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### Attachments
|
|
93
|
+
|
|
94
|
+
Up to 10 files per message, 10 MiB combined once decoded. `content` accepts a `Uint8Array` — the
|
|
95
|
+
SDK base64-encodes it — or a string that is already base64, sent as-is and never re-encoded (so it
|
|
96
|
+
is never corrupted by double-encoding).
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await posthaste.emails.send({
|
|
100
|
+
from: 'Acme <billing@acme.com>',
|
|
101
|
+
to: 'customer@example.com',
|
|
102
|
+
subject: 'Your receipt',
|
|
103
|
+
html: '<p>Thanks for your order.</p>',
|
|
104
|
+
attachments: [
|
|
105
|
+
{ filename: 'receipt.pdf', contentType: 'application/pdf', content: pdfBytes }, // Uint8Array
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
An inline image is the same shape with two extra fields — `disposition: 'inline'` and a `cid`,
|
|
111
|
+
referenced from the html as `cid:logo`:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
await posthaste.emails.send({
|
|
115
|
+
from: 'Acme <billing@acme.com>',
|
|
116
|
+
to: 'customer@example.com',
|
|
117
|
+
subject: 'Welcome',
|
|
118
|
+
html: '<p>Hello <img src="cid:logo"></p>',
|
|
119
|
+
attachments: [
|
|
120
|
+
{
|
|
121
|
+
filename: 'logo.png',
|
|
122
|
+
contentType: 'image/png',
|
|
123
|
+
content: logoBytes,
|
|
124
|
+
disposition: 'inline',
|
|
125
|
+
cid: 'logo',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Executable file types (`.exe`, `.js`, `.bat`, `.vbs`, `.msi`, and the rest of Gmail's blocked set)
|
|
132
|
+
are refused with `attachment_type_blocked`, checked against the **final** extension — so
|
|
133
|
+
`invoice.pdf.exe` is blocked. Read a stored attachment's bytes back with `downloadAttachment`:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const stored = message.content!.attachments[0]!; // metadata: id, filename, contentType, …
|
|
137
|
+
const file = await posthaste.messages.downloadAttachment(message.id, stored.id);
|
|
138
|
+
file.bytes; // Uint8Array — the exact bytes that were sent
|
|
139
|
+
file.contentType; // an inert type on a small allowlist, or application/octet-stream
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Scheduling
|
|
143
|
+
|
|
144
|
+
Add `scheduledAt` to send later instead of immediately. It must be **ISO-8601 with a timezone
|
|
145
|
+
offset** — `2026-08-25T09:00:00-05:00`, not `"tomorrow 9am"` — because that is unambiguous and it
|
|
146
|
+
is what `Date.toISOString()` already produces.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const result = await posthaste.emails.send({
|
|
150
|
+
from: 'Acme <billing@acme.com>',
|
|
151
|
+
to: 'customer@example.com',
|
|
152
|
+
subject: 'Renewal reminder',
|
|
153
|
+
text: 'Your plan renews in three days.',
|
|
154
|
+
scheduledAt: '2026-08-25T09:00:00-05:00',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
result.status; // 'scheduled'
|
|
158
|
+
result.scheduledAt; // '2026-08-25T14:00:00.000Z' — normalized to UTC
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
A timestamp within 60 seconds of now collapses to an ordinary immediate send. Beyond that, how far
|
|
162
|
+
ahead you can go depends on your plan, bounded platform-wide so nothing can schedule past the
|
|
163
|
+
30-day retention window; going past it throws `schedule_too_far` carrying `maxDays`, the limit that
|
|
164
|
+
actually applied.
|
|
165
|
+
|
|
166
|
+
Quota — suppression, domain verification, your daily and monthly allowance — is decided in full on
|
|
167
|
+
the day the mail actually leaves, not the day you scheduled it. Cancel any time before then:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
await posthaste.emails.cancelSchedule(result.id);
|
|
171
|
+
// { id, status: 'canceled' } — a retry after a lost response returns the same success again
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A cancel attempted after the message has already released throws `not_scheduled`, carrying the
|
|
175
|
+
message's current `status`.
|
|
176
|
+
|
|
92
177
|
### Two success statuses, and why you are told which
|
|
93
178
|
|
|
94
179
|
The API answers a **new** send with `202 { status: 'queued' }` and an **idempotency replay** with
|
|
@@ -143,32 +228,52 @@ Details worth knowing:
|
|
|
143
228
|
|
|
144
229
|
## Pagination
|
|
145
230
|
|
|
146
|
-
|
|
147
|
-
is the id of the last row you received, which is exactly
|
|
231
|
+
**Every list is paginated.** `limit` and `before` in, `{ data, nextCursor }` out — plus `hasMore` on
|
|
232
|
+
the three endpoints that send it. `before` is the id of the last row you received, which is exactly
|
|
233
|
+
what `nextCursor` gives you.
|
|
148
234
|
|
|
149
235
|
```ts
|
|
150
236
|
const page = await posthaste.messages.list({ limit: 50, status: 'bounced' });
|
|
151
237
|
// page.data, page.hasMore, page.nextCursor
|
|
152
238
|
```
|
|
153
239
|
|
|
154
|
-
|
|
240
|
+
| Resource | `limit` | Cursor | `hasMore`? |
|
|
241
|
+
| ------------------ | ------------------ | ----------- | ---------- |
|
|
242
|
+
| `messages` | 1–100, default 25 | `before` | yes |
|
|
243
|
+
| `suppressions` | 1–200, default 50 | `before` | yes |
|
|
244
|
+
| `domains` | 1–100, default 100 | `before` | **no** |
|
|
245
|
+
| `webhooks` | 1–100, default 50 | `before` | **no** |
|
|
246
|
+
| `apiKeys` | 1–100, default 50 | `before` | **no** |
|
|
247
|
+
| `billing.invoices` | 1–100, default 50 | `before` | **no** |
|
|
248
|
+
| `billing.history` | 1–200, default 100 | **`after`** | **no** |
|
|
249
|
+
|
|
250
|
+
### Check both signals, and neither on its own
|
|
155
251
|
|
|
156
|
-
|
|
157
|
-
on the final page**. The obvious loop —
|
|
252
|
+
The two obvious loops each break on a different endpoint, in opposite directions.
|
|
158
253
|
|
|
159
254
|
```ts
|
|
160
|
-
// WRONG. This can never terminate.
|
|
255
|
+
// WRONG, way one. This can never terminate.
|
|
256
|
+
// `nextCursor` is derived from the last row of a page, and /v1/inbound/messages
|
|
257
|
+
// returns a non-null cursor on its FINAL page — so this asks for the page after
|
|
258
|
+
// the last one, gets an empty page carrying the same cursor, and spins for ever.
|
|
161
259
|
let cursor: string | null | undefined = undefined;
|
|
162
260
|
do {
|
|
163
261
|
const page = await posthaste.messages.list({ before: cursor });
|
|
164
262
|
cursor = page.nextCursor;
|
|
165
263
|
} while (cursor);
|
|
264
|
+
|
|
265
|
+
// WRONG, way two. This stops after ONE page.
|
|
266
|
+
// domains, webhooks, apiKeys, billing.invoices and billing.history send no
|
|
267
|
+
// `hasMore` at all, so this reads `undefined`, treats it as "that was
|
|
268
|
+
// everything", and silently truncates. Compare with `=== false`.
|
|
269
|
+
if (!page.hasMore) return;
|
|
166
270
|
```
|
|
167
271
|
|
|
168
|
-
|
|
169
|
-
|
|
272
|
+
Neither failure produces an error. The first hangs; the second quietly gives you a partial list —
|
|
273
|
+
sixty invoices behind a default of fifty is fifty invoices and no way to tell.
|
|
170
274
|
|
|
171
|
-
`autoPaginate`
|
|
275
|
+
`autoPaginate` consults both signals and treats each as authoritative only where the server actually
|
|
276
|
+
sends it, so you cannot get either wrong:
|
|
172
277
|
|
|
173
278
|
```ts
|
|
174
279
|
for await (const message of posthaste.messages.autoPaginate({ status: 'bounced' })) {
|
|
@@ -176,8 +281,8 @@ for await (const message of posthaste.messages.autoPaginate({ status: 'bounced'
|
|
|
176
281
|
}
|
|
177
282
|
```
|
|
178
283
|
|
|
179
|
-
It stops on `hasMore === false`,
|
|
180
|
-
so a contract change upstream produces a short result, never an infinite loop.
|
|
284
|
+
It stops on `hasMore === false`, on a null cursor, on an empty page, and on a cursor that fails to
|
|
285
|
+
advance — so a contract change upstream produces a short result, never an infinite loop.
|
|
181
286
|
|
|
182
287
|
For the common "give me an array" case, with a ceiling you choose:
|
|
183
288
|
|
|
@@ -185,8 +290,31 @@ For the common "give me an array" case, with a ceiling you choose:
|
|
|
185
290
|
const recent = await posthaste.messages.listAll({ status: 'bounced' }, 500);
|
|
186
291
|
```
|
|
187
292
|
|
|
188
|
-
`suppressions
|
|
189
|
-
`
|
|
293
|
+
`messages`, `suppressions`, `domains`, `webhooks` and `apiKeys` all have the same three methods
|
|
294
|
+
(`list`, `autoPaginate`, `listAll`). `billing` names its two lists explicitly:
|
|
295
|
+
`invoices` / `autoPaginateInvoices` / `listAllInvoices`, and `history` / `autoPaginateHistory` /
|
|
296
|
+
`listAllHistory`.
|
|
297
|
+
|
|
298
|
+
### `billing.history` is the exception
|
|
299
|
+
|
|
300
|
+
It is the one list that reads **forward**, and it does not take `before` at all. The billing record
|
|
301
|
+
is a hash chain, a chain is verified from its start, and a record that renders in a different order
|
|
302
|
+
than it verifies in is one people stop trusting — so events come back **oldest first** and you
|
|
303
|
+
resume with `after`.
|
|
304
|
+
|
|
305
|
+
`after` is a **sequence number** (a `BillingEvent.seq`), not a prefixed id; passing an id is a
|
|
306
|
+
`400 invalid_request`. `nextCursor` is that number as a string, so passing it straight back is
|
|
307
|
+
correct.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
for await (const event of posthaste.billing.autoPaginateHistory()) {
|
|
311
|
+
console.log(event.seq, event.type);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`chain` travels with every page and verifies the **whole account history**, not the page you are
|
|
316
|
+
holding — so it means the same thing on page four as on page one. `autoPaginateHistory` yields only
|
|
317
|
+
the events; call `history()` if you need the verification too.
|
|
190
318
|
|
|
191
319
|
---
|
|
192
320
|
|
|
@@ -198,7 +326,7 @@ Register an endpoint and Posthaste posts events to it as they happen. The signin
|
|
|
198
326
|
```ts
|
|
199
327
|
const hook = await posthaste.webhooks.create({
|
|
200
328
|
url: 'https://acme.com/hooks/posthaste',
|
|
201
|
-
eventTypes: ['delivered', 'bounced', 'complained'], // omit for all
|
|
329
|
+
eventTypes: ['delivered', 'bounced', 'complained'], // omit for all thirteen
|
|
202
330
|
});
|
|
203
331
|
|
|
204
332
|
await store(hook.signingSecret); // whsec_… — you cannot read it again
|
|
@@ -271,8 +399,9 @@ verifyWebhook(rawBody, header, secret, { toleranceSeconds: 300 });
|
|
|
271
399
|
`parseWebhookEvent` verifies and JSON-parses in one step, returning `null` on any failure, for the
|
|
272
400
|
common case where a bad delivery just gets a 400.
|
|
273
401
|
|
|
274
|
-
The
|
|
275
|
-
`complained`, `failed`, `rejected`
|
|
402
|
+
The thirteen event types are `accepted`, `queued`, `attempted`, `delivered`, `deferred`, `bounced`,
|
|
403
|
+
`complained`, `failed`, `rejected`, `suppressed`, `scheduled`, `schedule_canceled` and
|
|
404
|
+
`schedule_failed` — exported as `EVENT_TYPES`.
|
|
276
405
|
|
|
277
406
|
---
|
|
278
407
|
|
|
@@ -307,7 +436,8 @@ switch (err.type) {
|
|
|
307
436
|
return permanent(err); // do not retry — fix the request
|
|
308
437
|
|
|
309
438
|
case 'rate_limited':
|
|
310
|
-
|
|
439
|
+
case 'platform_paused':
|
|
440
|
+
return retryAfter(err.retryAfterSeconds ?? 60); // transient; come back
|
|
311
441
|
|
|
312
442
|
case 'daily_limit_reached':
|
|
313
443
|
case 'monthly_limit_reached':
|
|
@@ -343,21 +473,30 @@ server sends one.
|
|
|
343
473
|
|
|
344
474
|
Two rules make this safe rather than merely automatic.
|
|
345
475
|
|
|
346
|
-
**It branches on `error.type`, not on the status.**
|
|
476
|
+
**It branches on `error.type`, not on the status.** Four different refusals arrive as `429`:
|
|
347
477
|
|
|
348
|
-
| type | what it means | `Retry-After` | retried?
|
|
349
|
-
| ----------------------- | ----------------------------------- | -------------- |
|
|
350
|
-
| `rate_limited` | too many requests for this key | seconds | yes
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
478
|
+
| type | what it means | `Retry-After` | classified as | retried? |
|
|
479
|
+
| ----------------------- | ----------------------------------- | -------------- | ------------------ | --------- |
|
|
480
|
+
| `rate_limited` | too many requests for this key | seconds | `isRateLimited` | yes |
|
|
481
|
+
| `platform_paused` | the PLATFORM's daily send ceiling | a few minutes | `isRateLimited` | see below |
|
|
482
|
+
| `daily_limit_reached` | today's warmup cap is spent | until midnight | `isQuotaExhausted` | **no** |
|
|
483
|
+
| `monthly_limit_reached` | the plan's monthly allowance is out | up to a month | `isQuotaExhausted` | **no** |
|
|
353
484
|
|
|
354
|
-
The
|
|
485
|
+
The bottom two are exhausted quota, not throttling. Retrying them in-process would hammer a wall the
|
|
355
486
|
calendar has to move before it opens, while burning your request-rate limit on the way. They are
|
|
356
487
|
raised immediately, with `err.isQuotaExhausted === true` and the wait on
|
|
357
488
|
`err.retryAfterSeconds`, so _you_ can decide — a queue, a delay, an alert.
|
|
358
489
|
|
|
359
|
-
|
|
360
|
-
|
|
490
|
+
`platform_paused` looks like them and is not one of them. It says the platform's own daily total is
|
|
491
|
+
full, not that you have spent anything: nothing about your account changes it, upgrading does not
|
|
492
|
+
clear it, and it frees as the day's total drains rather than on a calendar boundary. So it is
|
|
493
|
+
`isRateLimited`, never `isQuotaExhausted` — tell a customer to come back, not to upgrade.
|
|
494
|
+
|
|
495
|
+
A `Retry-After` longer than `maxRetryDelayMs` (60 seconds by default) is not slept through, whatever
|
|
496
|
+
its type: blocking a request handler for fifteen minutes is indistinguishable from a hang. The real
|
|
497
|
+
`platform_paused` ceiling sends about five minutes, so in practice it comes back to you to schedule —
|
|
498
|
+
that is `maxRetryDelayMs` judging the length of the wait, not the quota rule, and raising
|
|
499
|
+
`maxRetryDelayMs` makes the SDK sit through it.
|
|
361
500
|
|
|
362
501
|
**It never retries a request that repeating could duplicate.** Concretely, a send is retried **only
|
|
363
502
|
when you supplied an `idempotencyKey`**, and `webhooks.create` is never retried (a duplicate
|
|
@@ -377,7 +516,9 @@ account.verify() GET /v1/account/verify
|
|
|
377
516
|
account.usage() GET /v1/usage
|
|
378
517
|
|
|
379
518
|
domains.create({ name }) POST /v1/domains
|
|
380
|
-
domains.list()
|
|
519
|
+
domains.list(params) GET /v1/domains
|
|
520
|
+
domains.autoPaginate(params) GET /v1/domains (all pages)
|
|
521
|
+
domains.listAll(params, maxItems) GET /v1/domains (all pages)
|
|
381
522
|
domains.verify(id) POST /v1/domains/:id/verify
|
|
382
523
|
domains.delete(id) DELETE /v1/domains/:id
|
|
383
524
|
domains.setup(id) GET /v1/domains/:id/setup
|
|
@@ -385,11 +526,13 @@ domains.connectCloudflare(id, { token }) POST /v1/domains/:id/cloudflare
|
|
|
385
526
|
domains.disconnectCloudflare() DELETE /v1/account/cloudflare
|
|
386
527
|
|
|
387
528
|
emails.send(params) POST /v1/emails
|
|
529
|
+
emails.cancelSchedule(id) DELETE /v1/emails/:id/schedule
|
|
388
530
|
|
|
389
531
|
messages.list(params) GET /v1/messages
|
|
390
532
|
messages.autoPaginate(params) GET /v1/messages (all pages)
|
|
391
533
|
messages.listAll(params, maxItems) GET /v1/messages (all pages)
|
|
392
534
|
messages.get(id) GET /v1/messages/:id
|
|
535
|
+
messages.downloadAttachment(msgId, attId) GET /v1/messages/:id/attachments/:attachmentId
|
|
393
536
|
messages.stats(params) GET /v1/stats/messages
|
|
394
537
|
|
|
395
538
|
suppressions.list(params) GET /v1/suppressions
|
|
@@ -399,14 +542,22 @@ suppressions.create({ address, reason }) POST /v1/suppressions
|
|
|
399
542
|
suppressions.delete(address) DELETE /v1/suppressions/:address
|
|
400
543
|
|
|
401
544
|
webhooks.create({ url, eventTypes }) POST /v1/webhooks
|
|
402
|
-
webhooks.list()
|
|
545
|
+
webhooks.list(params) GET /v1/webhooks
|
|
546
|
+
webhooks.autoPaginate(params) GET /v1/webhooks (all pages)
|
|
547
|
+
webhooks.listAll(params, maxItems) GET /v1/webhooks (all pages)
|
|
403
548
|
webhooks.delete(id) DELETE /v1/webhooks/:id
|
|
404
549
|
|
|
405
|
-
apiKeys.list()
|
|
550
|
+
apiKeys.list(params) GET /v1/api-keys
|
|
551
|
+
apiKeys.autoPaginate(params) GET /v1/api-keys (all pages)
|
|
552
|
+
apiKeys.listAll(params, maxItems) GET /v1/api-keys (all pages)
|
|
406
553
|
|
|
407
554
|
billing.get() GET /v1/billing
|
|
408
|
-
billing.history()
|
|
409
|
-
billing.
|
|
555
|
+
billing.history(params) GET /v1/billing/history (`after`, oldest first)
|
|
556
|
+
billing.autoPaginateHistory(params) GET /v1/billing/history (all pages)
|
|
557
|
+
billing.listAllHistory(params, maxItems) GET /v1/billing/history (all pages)
|
|
558
|
+
billing.invoices(params) GET /v1/billing/invoices
|
|
559
|
+
billing.autoPaginateInvoices(params) GET /v1/billing/invoices (all pages)
|
|
560
|
+
billing.listAllInvoices(params, maxItems) GET /v1/billing/invoices (all pages)
|
|
410
561
|
billing.invoice(id) GET /v1/billing/invoices/:id
|
|
411
562
|
```
|
|
412
563
|
|
package/dist/base64.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure base64 encoder for attachment bytes.
|
|
3
|
+
*
|
|
4
|
+
* Hand-rolled on purpose: this package ships with zero dependencies (enforced
|
|
5
|
+
* by packaging.test.ts), `Buffer` is Node-only and this SDK also runs bundled
|
|
6
|
+
* in edge runtimes, and `btoa` chokes on code points over 0xff unless the
|
|
7
|
+
* input is first mangled through a binary string. Fifteen lines beats any of
|
|
8
|
+
* those failure modes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function bytesToBase64(bytes: Uint8Array): string;
|
package/dist/base64.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure base64 encoder for attachment bytes.
|
|
3
|
+
*
|
|
4
|
+
* Hand-rolled on purpose: this package ships with zero dependencies (enforced
|
|
5
|
+
* by packaging.test.ts), `Buffer` is Node-only and this SDK also runs bundled
|
|
6
|
+
* in edge runtimes, and `btoa` chokes on code points over 0xff unless the
|
|
7
|
+
* input is first mangled through a binary string. Fifteen lines beats any of
|
|
8
|
+
* those failure modes.
|
|
9
|
+
*/
|
|
10
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
11
|
+
export function bytesToBase64(bytes) {
|
|
12
|
+
let out = '';
|
|
13
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
14
|
+
const a = bytes[i];
|
|
15
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
16
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
17
|
+
out += ALPHABET[a >> 2] + ALPHABET[((a & 0x03) << 4) | (b >> 4)];
|
|
18
|
+
out += i + 1 < bytes.length ? ALPHABET[((b & 0x0f) << 2) | (c >> 6)] : '=';
|
|
19
|
+
out += i + 2 < bytes.length ? ALPHABET[c & 0x3f] : '=';
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* is absent because a customer cannot use it — see the README.
|
|
13
13
|
*/
|
|
14
14
|
import { HttpClient, type PosthasteOptions, type RequestOptions } from './http.js';
|
|
15
|
-
import type { Account, Billing, BillingHistory, ChainVerification, CloudflarePublishResult, ConnectCloudflareParams, CreateDomainParams, CreateSuppressionParams, CreateWebhookParams, CreatedDomain, CreatedSuppression, CreatedWebhook, Domain, DomainSetup, DomainVerification, Invoice, InvoiceDetail,
|
|
15
|
+
import type { Account, Billing, BillingEvent, BillingHistory, ChainVerification, CloudflarePublishResult, ConnectCloudflareParams, CreateDomainParams, CreateSuppressionParams, CreateWebhookParams, CreatedDomain, CreatedSuppression, CreatedWebhook, CursorPage, Domain, DomainSetup, DomainVerification, Invoice, InvoiceDetail, ApiKey, ListApiKeysParams, ListBillingHistoryParams, ListDomainsParams, ListInvoicesParams, ListMessagesParams, ListSuppressionsParams, ListWebhooksParams, Message, MessageStats, MessageStatsParams, MessageSummary, Page, SendEmailParams, SendEmailResult, Suppression, Usage, Webhook } from './types.js';
|
|
16
16
|
export declare class Posthaste {
|
|
17
17
|
private readonly http;
|
|
18
18
|
readonly account: AccountResource;
|
|
@@ -52,8 +52,19 @@ export declare class DomainsResource {
|
|
|
52
52
|
* response cannot leave duplicates behind.
|
|
53
53
|
*/
|
|
54
54
|
create(params: CreateDomainParams, options?: RequestOptions): Promise<CreatedDomain>;
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/**
|
|
56
|
+
* `GET /v1/domains` — one keyset page, newest first.
|
|
57
|
+
*
|
|
58
|
+
* `limit` defaults to 100, which is also its maximum, so on most accounts the
|
|
59
|
+
* first page is every domain. It is still a page: past a hundred domains the
|
|
60
|
+
* rest are behind `nextCursor`, and there is no `hasMore` here to tell you so.
|
|
61
|
+
* Use `autoPaginate` if the count is not bounded by something you control.
|
|
62
|
+
*/
|
|
63
|
+
list(params?: ListDomainsParams, options?: RequestOptions): Promise<CursorPage<Domain>>;
|
|
64
|
+
/** Every domain, across every page. */
|
|
65
|
+
autoPaginate(params?: ListDomainsParams, options?: RequestOptions): AsyncGenerator<Domain, void, undefined>;
|
|
66
|
+
/** Drain `autoPaginate` into an array, up to `maxItems`. */
|
|
67
|
+
listAll(params?: ListDomainsParams, maxItems?: number, options?: RequestOptions): Promise<Domain[]>;
|
|
57
68
|
/**
|
|
58
69
|
* `POST /v1/domains/:id/verify` — look for the records and record the result.
|
|
59
70
|
*
|
|
@@ -120,6 +131,18 @@ export declare class EmailsResource {
|
|
|
120
131
|
* the header.
|
|
121
132
|
*/
|
|
122
133
|
send(params: SendEmailParams, options?: RequestOptions): Promise<SendEmailResult>;
|
|
134
|
+
/**
|
|
135
|
+
* `DELETE /v1/emails/:id/schedule` — cancel a scheduled send.
|
|
136
|
+
*
|
|
137
|
+
* Succeeds until the message is released to delivery (which happens within
|
|
138
|
+
* about a minute of its scheduled time). A message that already released
|
|
139
|
+
* throws `not_scheduled` carrying its current status; a cancel retried
|
|
140
|
+
* after a lost response returns success again rather than erroring.
|
|
141
|
+
*/
|
|
142
|
+
cancelSchedule(id: string, options?: RequestOptions): Promise<{
|
|
143
|
+
id: string;
|
|
144
|
+
status: 'canceled';
|
|
145
|
+
}>;
|
|
123
146
|
}
|
|
124
147
|
export declare class MessagesResource {
|
|
125
148
|
private readonly http;
|
|
@@ -140,6 +163,19 @@ export declare class MessagesResource {
|
|
|
140
163
|
* linkage, with the chain re-verified on read (`recordIntact`).
|
|
141
164
|
*/
|
|
142
165
|
get(id: string, options?: RequestOptions): Promise<Message>;
|
|
166
|
+
/**
|
|
167
|
+
* `GET /v1/messages/:id/attachments/:attachmentId` — the stored bytes.
|
|
168
|
+
*
|
|
169
|
+
* The attachment id comes from `get(id).content.attachments[].id`. Returns
|
|
170
|
+
* the exact bytes that were sent; the server serves them with
|
|
171
|
+
* `Content-Disposition: attachment` and only ever an inert content type,
|
|
172
|
+
* echoed here for callers who re-serve the file.
|
|
173
|
+
*/
|
|
174
|
+
downloadAttachment(messageId: string, attachmentId: string, options?: RequestOptions): Promise<{
|
|
175
|
+
bytes: Uint8Array;
|
|
176
|
+
contentType: string;
|
|
177
|
+
filename: string | null;
|
|
178
|
+
}>;
|
|
143
179
|
/**
|
|
144
180
|
* `GET /v1/stats/messages` — daily volume with the previous window for
|
|
145
181
|
* comparison.
|
|
@@ -195,8 +231,17 @@ export declare class WebhooksResource {
|
|
|
195
231
|
* duplicate would then receive every event twice.
|
|
196
232
|
*/
|
|
197
233
|
create(params: CreateWebhookParams, options?: RequestOptions): Promise<CreatedWebhook>;
|
|
198
|
-
/**
|
|
199
|
-
|
|
234
|
+
/**
|
|
235
|
+
* `GET /v1/webhooks` — one keyset page, newest first. Never includes secrets.
|
|
236
|
+
*
|
|
237
|
+
* `limit` defaults to 50 and caps at 100. There is no `hasMore` on this
|
|
238
|
+
* endpoint; `nextCursor` is the end-of-list signal.
|
|
239
|
+
*/
|
|
240
|
+
list(params?: ListWebhooksParams, options?: RequestOptions): Promise<CursorPage<Webhook>>;
|
|
241
|
+
/** Every webhook, across every page. */
|
|
242
|
+
autoPaginate(params?: ListWebhooksParams, options?: RequestOptions): AsyncGenerator<Webhook, void, undefined>;
|
|
243
|
+
/** Drain `autoPaginate` into an array, up to `maxItems`. */
|
|
244
|
+
listAll(params?: ListWebhooksParams, maxItems?: number, options?: RequestOptions): Promise<Webhook[]>;
|
|
200
245
|
/** `DELETE /v1/webhooks/:id` — 204, or 404 for an unknown id. */
|
|
201
246
|
delete(id: string, options?: RequestOptions): Promise<void>;
|
|
202
247
|
}
|
|
@@ -204,14 +249,22 @@ export declare class ApiKeysResource {
|
|
|
204
249
|
private readonly http;
|
|
205
250
|
constructor(http: HttpClient);
|
|
206
251
|
/**
|
|
207
|
-
* `GET /v1/api-keys` —
|
|
252
|
+
* `GET /v1/api-keys` — one keyset page of keys, revoked ones included.
|
|
253
|
+
*
|
|
254
|
+
* `limit` defaults to 50 and caps at 100, and revoked keys count towards it —
|
|
255
|
+
* so an account that has rotated its credentials a few times will have more
|
|
256
|
+
* than one page. No `hasMore`; `nextCursor` is the end-of-list signal.
|
|
208
257
|
*
|
|
209
258
|
* Read only, and that is the whole resource. Creating and revoking keys
|
|
210
259
|
* requires a signed-in owner or admin and refuses a Bearer key outright: a
|
|
211
260
|
* server-side credential that could mint more credentials would make every
|
|
212
261
|
* narrow key one request away from a full one.
|
|
213
262
|
*/
|
|
214
|
-
list(options?: RequestOptions): Promise<
|
|
263
|
+
list(params?: ListApiKeysParams, options?: RequestOptions): Promise<CursorPage<ApiKey>>;
|
|
264
|
+
/** Every key, across every page. */
|
|
265
|
+
autoPaginate(params?: ListApiKeysParams, options?: RequestOptions): AsyncGenerator<ApiKey, void, undefined>;
|
|
266
|
+
/** Drain `autoPaginate` into an array, up to `maxItems`. */
|
|
267
|
+
listAll(params?: ListApiKeysParams, maxItems?: number, options?: RequestOptions): Promise<ApiKey[]>;
|
|
215
268
|
}
|
|
216
269
|
export declare class BillingResource {
|
|
217
270
|
private readonly http;
|
|
@@ -225,10 +278,48 @@ export declare class BillingResource {
|
|
|
225
278
|
* gets you in. Money is always in minor units.
|
|
226
279
|
*/
|
|
227
280
|
get(options?: RequestOptions): Promise<Billing>;
|
|
228
|
-
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
281
|
+
/**
|
|
282
|
+
* `GET /v1/billing/history` — one page of the hash-chained commercial record,
|
|
283
|
+
* plus its verification.
|
|
284
|
+
*
|
|
285
|
+
* THE ODD ONE OUT, in two ways that a caller who assumes the house style will
|
|
286
|
+
* get wrong:
|
|
287
|
+
*
|
|
288
|
+
* - It reads FORWARD. Events come back oldest first, and the page resumes
|
|
289
|
+
* from `after`, not `before`. That is not a stylistic choice: a hash chain
|
|
290
|
+
* is verified from its start, and a record that renders in a different
|
|
291
|
+
* order than it verifies in is one people stop trusting.
|
|
292
|
+
* - `after` is a SEQUENCE NUMBER — a `BillingEvent.seq` — not a prefixed id.
|
|
293
|
+
* Passing an id gets `400 invalid_request`. `nextCursor` is that number
|
|
294
|
+
* rendered as a string, so passing it straight back is correct.
|
|
295
|
+
*
|
|
296
|
+
* `limit` defaults to 100 and caps at 200. `chain` is verified over the whole
|
|
297
|
+
* account history rather than over the page, so it means the same thing on
|
|
298
|
+
* every page.
|
|
299
|
+
*/
|
|
300
|
+
history(params?: ListBillingHistoryParams, options?: RequestOptions): Promise<BillingHistory>;
|
|
301
|
+
/**
|
|
302
|
+
* Every billing event, oldest first, across every page.
|
|
303
|
+
*
|
|
304
|
+
* Note that this yields the EVENTS only. `chain` is per-response, so if you
|
|
305
|
+
* need the verification as well, call `history` and read it from there.
|
|
306
|
+
*/
|
|
307
|
+
autoPaginateHistory(params?: ListBillingHistoryParams, options?: RequestOptions): AsyncGenerator<BillingEvent, void, undefined>;
|
|
308
|
+
/** Drain `autoPaginateHistory` into an array, up to `maxItems`. */
|
|
309
|
+
listAllHistory(params?: ListBillingHistoryParams, maxItems?: number, options?: RequestOptions): Promise<BillingEvent[]>;
|
|
310
|
+
/**
|
|
311
|
+
* `GET /v1/billing/invoices` — one keyset page, newest first.
|
|
312
|
+
*
|
|
313
|
+
* `limit` defaults to 50 and caps at 100, and there is no `hasMore` here. An
|
|
314
|
+
* account past its fiftieth invoice that reads only `data` is silently short
|
|
315
|
+
* of its own financial record — follow `nextCursor`, or use
|
|
316
|
+
* `autoPaginateInvoices`.
|
|
317
|
+
*/
|
|
318
|
+
invoices(params?: ListInvoicesParams, options?: RequestOptions): Promise<CursorPage<Invoice>>;
|
|
319
|
+
/** Every invoice, across every page. */
|
|
320
|
+
autoPaginateInvoices(params?: ListInvoicesParams, options?: RequestOptions): AsyncGenerator<Invoice, void, undefined>;
|
|
321
|
+
/** Drain `autoPaginateInvoices` into an array, up to `maxItems`. */
|
|
322
|
+
listAllInvoices(params?: ListInvoicesParams, maxItems?: number, options?: RequestOptions): Promise<Invoice[]>;
|
|
232
323
|
/** `GET /v1/billing/invoices/:id` — the same document plus the supplier block. */
|
|
233
324
|
invoice(id: string, options?: RequestOptions): Promise<InvoiceDetail>;
|
|
234
325
|
}
|