@openemail/cli 0.0.2 → 0.0.3
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 +1 -1
- package/openemail.js +131 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ openemail temp read
|
|
|
126
126
|
openemail temp delete --yes
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
A disposable inbox needs no account and no key. `temp new` prints the address, and the CLI keeps the inbox's token so the other `temp` commands find it by its id or address. `temp watch --first` waits for the first message, which suits a script waiting for a sign-up code.
|
|
129
|
+
A disposable inbox needs no account and no key. `temp new` prints the address, and the CLI keeps the inbox's token so the other `temp` commands find it by its id or address. `temp watch --first` waits for the first message, which suits a script waiting for a sign-up code. `temp-mail extend` adds up to an hour, within 24 hours of the inbox being created, and the CLI keeps the new token it returns. `temp delete` moves the mail to the bin and forgets the token here, while the lease runs on until it expires.
|
|
130
130
|
|
|
131
131
|
### Every resource
|
|
132
132
|
```bash
|
package/openemail.js
CHANGED
|
@@ -1112,7 +1112,7 @@ const BOOLEAN_TRUE_WORDS = new Set(['true', '1', 'yes', 'on']);
|
|
|
1112
1112
|
const BOOLEAN_FALSE_WORDS = new Set(['false', '0', 'no', 'off']);
|
|
1113
1113
|
const NEGATIVE_NUMBER_PATTERN = /^-\d+(\.\d+)?$/;
|
|
1114
1114
|
|
|
1115
|
-
var version$1 = "0.0.
|
|
1115
|
+
var version$1 = "0.0.3";
|
|
1116
1116
|
|
|
1117
1117
|
const VERSION$1 = version$1;
|
|
1118
1118
|
const CLI_NAME = 'openemail';
|
|
@@ -1245,7 +1245,7 @@ const SCRIPTING_ROWS = [
|
|
|
1245
1245
|
const HELP_JSON_SCHEMA_VERSION = 1;
|
|
1246
1246
|
const CLI_NAME_WORD_PATTERN = new RegExp(`(^|[^\\w-])${CLI_NAME}($|[^\\w-])`);
|
|
1247
1247
|
|
|
1248
|
-
var version = "0.0.
|
|
1248
|
+
var version = "0.0.7";
|
|
1249
1249
|
|
|
1250
1250
|
const BUILD_BASE_URL = 'https://api.openemail.uk';
|
|
1251
1251
|
|
|
@@ -7501,14 +7501,14 @@ const TempMailCreateMethod = {
|
|
|
7501
7501
|
slug: 'temp-mail/create',
|
|
7502
7502
|
signature: 'create(body?: TempInboxCreate, options?: InboxScope): Promise<CreatedTempInboxResource>',
|
|
7503
7503
|
summary: 'Create a disposable inbox and its access token',
|
|
7504
|
-
description: 'Mints a disposable address and resolves with the inbox plus its `token`. It needs no credential, and this
|
|
7504
|
+
description: 'Mints a disposable address and resolves with the inbox plus its `token`. It needs no credential, and only this call and `extend` return a token. The token is the lease itself, signed, and nothing about it is stored on the server, so a lost token cannot be recovered, and every other method except `listDomains` needs it. Store it before you show the address to anyone.\n\nThe body is optional and so is every field in it. With nothing, you get a 12 character generated local part on the first domain in the pool, leased for 60 minutes. A chosen `localPart` is lowercased and must be up to 64 letters, digits, dots, dashes or underscores, starting and ending with a letter or digit, or it is 422 `invalid_address`, and one longer than 64 characters is 422 `invalid_parameter`. Names such as `postmaster`, `abuse` and `support` are 422 `reserved_address`, and an install with no pooled domain answers 503 `not_configured`.\n\nNothing is rate limited and nothing reserves an address, so this never answers 429 or 409. A name you choose is issued to anyone who asks for it, and each of you reads the mail that reaches it from the start of your own lease. Leave `localPart` out when the mail should reach you alone.',
|
|
7505
7505
|
method: 'POST',
|
|
7506
7506
|
path: '/temp-mail/inboxes',
|
|
7507
7507
|
scopes: [],
|
|
7508
7508
|
auth: 'none',
|
|
7509
7509
|
parameters: [
|
|
7510
|
-
{ name: 'body.domain', type: 'string', required: false, description: 'A domain from `listDomains`. Omit it for
|
|
7511
|
-
{ name: 'body.localPart', type: 'string', required: false, description: 'The part before the @. Omit it for a generated one, which
|
|
7510
|
+
{ name: 'body.domain', type: 'string', required: false, description: 'A domain from `listDomains`. Omit it for the first one in the pool. Any other name is 422 `unknown_domain` rather than a silent substitute.' },
|
|
7511
|
+
{ name: 'body.localPart', type: 'string', required: false, description: 'The part before the @. Omit it for a generated one, which nobody else is likely to be issued.' },
|
|
7512
7512
|
{ name: 'body.ttlMinutes', type: 'number', required: false, description: 'Lease length from now, a whole number from 1 to 1440. Defaults to 60. Out of range is 422 `invalid_parameter`, not clamped.' },
|
|
7513
7513
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7514
7514
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'Ignored and never sent. Creating an inbox is anonymous and hands you the token instead.' }
|
|
@@ -7517,8 +7517,8 @@ const TempMailCreateMethod = {
|
|
|
7517
7517
|
example: 'const temp = createTempMail()\n\nconst inbox = await temp.create({ ttlMinutes: 120 })\n\nconsole.log(inbox.address, inbox.expiresAt)\nconsole.log(inbox.token)',
|
|
7518
7518
|
notes: [
|
|
7519
7519
|
'Not retried automatically. A retry would mint a second inbox, and the first would be unreachable because its only token was in the lost response.',
|
|
7520
|
-
'
|
|
7521
|
-
'A lease of 1440 minutes reaches the 24 hour ceiling at once,
|
|
7520
|
+
'Nothing holds an address back after its lease ends or its inbox is deleted, so it can be issued again at once, to anybody.',
|
|
7521
|
+
'A lease of 1440 minutes reaches the 24 hour ceiling at once, yet the inbox still comes back with `extensionsLeft: 23`, because that counts calls rather than time. None of them can add a minute.',
|
|
7522
7522
|
'Unknown body keys are 422 `invalid_parameter`, while a body that is not valid JSON is treated as an empty one.'
|
|
7523
7523
|
]
|
|
7524
7524
|
};
|
|
@@ -7530,21 +7530,21 @@ const TempMailDeleteMessageMethod = {
|
|
|
7530
7530
|
slug: 'temp-mail/deleteMessage',
|
|
7531
7531
|
signature: 'deleteMessage(inboxId: string, messageId: string, options?: InboxScope): Promise<DeletedTempMessageResource>',
|
|
7532
7532
|
summary: 'Delete one message from a disposable inbox',
|
|
7533
|
-
description: '
|
|
7533
|
+
description: 'Moves the message, attachments and all, to the bin of the mailbox that runs the pool, and no lease lists or opens it again. Nothing in this SDK restores it.\n\n`messageCount` goes down by one. There is never a slot to free: an inbox keeps every message that reaches it, and `listMessages` pages through all of them.',
|
|
7534
7534
|
method: 'DELETE',
|
|
7535
7535
|
path: '/temp-mail/inboxes/{id}/messages/{messageId}',
|
|
7536
7536
|
scopes: [],
|
|
7537
7537
|
auth: 'inboxToken',
|
|
7538
7538
|
parameters: [
|
|
7539
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7540
|
-
{ name: 'messageId', type: 'string', required: true, description: '
|
|
7539
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7540
|
+
{ name: 'messageId', type: 'string', required: true, description: 'An `id` from `listMessages`, such as `thr_` and 24 hex.' },
|
|
7541
7541
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7542
7542
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'The `oe_inbox_` token `create` returned. Overrides the token given to `createTempMail` for this call, and is required on the `OpenEmail` client, which would otherwise send its API key.' }
|
|
7543
7543
|
],
|
|
7544
7544
|
returns: '`DeletedTempMessageResource` with `object` set to `temp_message`, the message `id` and `deleted: true`.',
|
|
7545
7545
|
example: 'const temp = createTempMail()\n\nconst inbox = await temp.create()\n\nconst { items } = await temp.listMessages(inbox.id, { inboxToken: inbox.token })\n\nfor (const message of items.filter((row) => row.spam)) {\n await temp.deleteMessage(inbox.id, message.id, { inboxToken: inbox.token })\n}',
|
|
7546
7546
|
notes: [
|
|
7547
|
-
'An unknown or already deleted message id is 404 `resource_not_found`.',
|
|
7547
|
+
'An unknown or already deleted message id is 404 `resource_not_found`, and an install with no key to read the pool answers 503 `not_configured`.',
|
|
7548
7548
|
'Not retried automatically. If you repeat it yourself after a lost response, that 404 means the first attempt already worked.'
|
|
7549
7549
|
]
|
|
7550
7550
|
};
|
|
@@ -7555,14 +7555,14 @@ const TempMailDeleteMethod = {
|
|
|
7555
7555
|
accessor: 'openemail.tempMail.delete',
|
|
7556
7556
|
slug: 'temp-mail/delete',
|
|
7557
7557
|
signature: 'delete(inboxId: string, options?: InboxScope): Promise<DeletedTempInboxResource>',
|
|
7558
|
-
summary: '
|
|
7559
|
-
description: '
|
|
7558
|
+
summary: 'Move the mail in a disposable inbox to the bin now',
|
|
7559
|
+
description: 'Moves every message the inbox shows to the bin of the mailbox that runs the pool, at once. It does not end the lease: nothing about a lease is stored, so there is nothing to revoke, and the token keeps opening the address until its expiry. Mail that arrives afterwards is listed as usual.\n\nNothing holds the address back either, so it can be issued again at once, to anybody. An install with no key to read the pool answers 503 `not_configured`.\n\nThe response is a tombstone rather than an empty body, so a log line can name what went.',
|
|
7560
7560
|
method: 'DELETE',
|
|
7561
7561
|
path: '/temp-mail/inboxes/{id}',
|
|
7562
7562
|
scopes: [],
|
|
7563
7563
|
auth: 'inboxToken',
|
|
7564
7564
|
parameters: [
|
|
7565
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7565
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7566
7566
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7567
7567
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'The `oe_inbox_` token `create` returned. Overrides the token given to `createTempMail` for this call, and is required on the `OpenEmail` client, which would otherwise send its API key.' }
|
|
7568
7568
|
],
|
|
@@ -7570,7 +7570,7 @@ const TempMailDeleteMethod = {
|
|
|
7570
7570
|
example: 'const temp = createTempMail()\n\nconst inbox = await temp.create()\n\nconst result = await temp.delete(inbox.id, { inboxToken: inbox.token })\n\nconsole.log(result.destroyed)',
|
|
7571
7571
|
notes: [
|
|
7572
7572
|
'The flag is `destroyed`, not `deleted` as on other tombstones.',
|
|
7573
|
-
'Not retried automatically.
|
|
7573
|
+
'Not retried automatically. Repeating it is harmless: the lease still stands, so a second call answers 200 and moves whatever has arrived since.'
|
|
7574
7574
|
]
|
|
7575
7575
|
};
|
|
7576
7576
|
|
|
@@ -7579,23 +7579,23 @@ const TempMailExtendMethod = {
|
|
|
7579
7579
|
name: 'extend',
|
|
7580
7580
|
accessor: 'openemail.tempMail.extend',
|
|
7581
7581
|
slug: 'temp-mail/extend',
|
|
7582
|
-
signature: 'extend(inboxId: string, options?: InboxScope): Promise<
|
|
7582
|
+
signature: 'extend(inboxId: string, options?: InboxScope): Promise<ExtendedTempInboxResource>',
|
|
7583
7583
|
summary: 'Push the expiry of an inbox an hour further out',
|
|
7584
|
-
description: 'Adds up to 60 minutes to `expiresAt` and resolves with the updated inbox. There is no body
|
|
7584
|
+
description: 'Adds up to 60 minutes to `expiresAt` and resolves with the updated inbox and a new `token` that carries the later expiry. There is no body. The old token keeps its old expiry, so use the new one from here on, including in a client built with `createTempMail({ inboxToken })`.\n\nThe new expiry is the earlier of one hour past the current expiry and 24 hours after `createdAt`, and a lease allows at most 23 extensions. The last extension can buy less than an hour, and on a lease that already reaches the 24 hours a call still succeeds, spends an extension and buys nothing. `extensionsLeft` counts only the 23 calls, so compare `expiresAt` with `createdAt` before offering more time.\n\nWhen `extensionsLeft` is 0 this answers 422 `extension_limit` for good, and the only way on is a new inbox. The response reads no mail, so its `messageCount` is 0 and its `lastMessageAt` is null.',
|
|
7585
7585
|
method: 'POST',
|
|
7586
7586
|
path: '/temp-mail/inboxes/{id}/extend',
|
|
7587
7587
|
scopes: [],
|
|
7588
7588
|
auth: 'inboxToken',
|
|
7589
7589
|
parameters: [
|
|
7590
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7590
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7591
7591
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7592
7592
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'The `oe_inbox_` token `create` returned. Overrides the token given to `createTempMail` for this call, and is required on the `OpenEmail` client, which would otherwise send its API key.' }
|
|
7593
7593
|
],
|
|
7594
|
-
returns: '`TempInboxResource` with the new `expiresAt` and the updated `extensionsLeft`.',
|
|
7595
|
-
example: 'const temp = createTempMail({ inboxToken: \'oe_inbox_Vb3kT9qLm2Xw7RzN4pYc6HfJ1sGa5Ed8KuQo0iWnS2e\' })\n\nconst inbox = await temp.extend(\'
|
|
7594
|
+
returns: '`ExtendedTempInboxResource`: the `TempInboxResource` fields with the new `expiresAt` and the updated `extensionsLeft`, plus the new `token` beginning `oe_inbox_`.',
|
|
7595
|
+
example: 'const temp = createTempMail({ inboxToken: \'oe_inbox_Vb3kT9qLm2Xw7RzN4pYc6HfJ1sGa5Ed8KuQo0iWnS2e\' })\n\nconst inbox = await temp.extend(\'tinb_k7m2q9xw4bdp\')\n\nconsole.log(inbox.expiresAt, inbox.extensionsLeft)\n\nconst renewed = createTempMail({ inboxToken: inbox.token })',
|
|
7596
7596
|
notes: [
|
|
7597
7597
|
'Not retried automatically, because a replay would spend a second extension.',
|
|
7598
|
-
'An inbox created with `ttlMinutes: 1440`
|
|
7598
|
+
'An inbox created with `ttlMinutes: 1440` still reports `extensionsLeft: 23`, and none of them can add a minute.'
|
|
7599
7599
|
]
|
|
7600
7600
|
};
|
|
7601
7601
|
|
|
@@ -7606,21 +7606,21 @@ const TempMailGetMessageMethod = {
|
|
|
7606
7606
|
slug: 'temp-mail/getMessage',
|
|
7607
7607
|
signature: 'getMessage(inboxId: string, messageId: string, options?: InboxScope): Promise<TempMessageDetailResource>',
|
|
7608
7608
|
summary: 'Read one message with its stored body',
|
|
7609
|
-
description: 'Resolves the list row for one message plus the parsed message as stored. Reading
|
|
7609
|
+
description: 'Resolves the list row for one message plus the parsed message as stored. Reading it does not mark it seen: `seen` mirrors the unread state of the message in the mailbox that runs the pool, and nothing on these routes changes it.\n\nRender `message.decodedBody`. `body` and `processedHtml` are empty strings for every message that can reach a disposable inbox, so a client reading either shows a blank page. The body is never cut, so `truncated` is always false.\n\nThe HTML came from a stranger to an address anyone could name. Render it outside your own origin, for example in a sandboxed iframe.',
|
|
7610
7610
|
method: 'GET',
|
|
7611
7611
|
path: '/temp-mail/inboxes/{id}/messages/{messageId}',
|
|
7612
7612
|
scopes: [],
|
|
7613
7613
|
auth: 'inboxToken',
|
|
7614
7614
|
parameters: [
|
|
7615
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7616
|
-
{ name: 'messageId', type: 'string', required: true, description: '
|
|
7615
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7616
|
+
{ name: 'messageId', type: 'string', required: true, description: 'An `id` from `listMessages`, such as `thr_` and 24 hex.' },
|
|
7617
7617
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7618
7618
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'The `oe_inbox_` token `create` returned. Overrides the token given to `createTempMail` for this call, and is required on the `OpenEmail` client, which would otherwise send its API key.' }
|
|
7619
7619
|
],
|
|
7620
7620
|
returns: '`TempMessageDetailResource`: every `TempMessageResource` field plus `message`, the stored parsed message, and `truncated`.',
|
|
7621
|
-
example: 'const temp = createTempMail({ inboxToken: \'oe_inbox_Vb3kT9qLm2Xw7RzN4pYc6HfJ1sGa5Ed8KuQo0iWnS2e\' })\n\nconst detail = await temp.getMessage(\'
|
|
7621
|
+
example: 'const temp = createTempMail({ inboxToken: \'oe_inbox_Vb3kT9qLm2Xw7RzN4pYc6HfJ1sGa5Ed8KuQo0iWnS2e\' })\n\nconst detail = await temp.getMessage(\'tinb_k7m2q9xw4bdp\', \'thr_9e3b7c1a5f2d8e40b6a9c3f1\')\n\nconsole.log(detail.subject, detail.truncated)\nconsole.log(detail.message.decodedBody)',
|
|
7622
7622
|
notes: [
|
|
7623
|
-
'
|
|
7623
|
+
'A message this lease cannot see, deleted ones included, is 404 `resource_not_found`. An expired lease is 401 `inbox_expired`, and an install with no key to read the pool answers 503 `not_configured`.',
|
|
7624
7624
|
'`message` is typed as a loose record, so narrow `decodedBody` to a string before rendering it.'
|
|
7625
7625
|
]
|
|
7626
7626
|
};
|
|
@@ -7632,20 +7632,20 @@ const TempMailGetMethod = {
|
|
|
7632
7632
|
slug: 'temp-mail/get',
|
|
7633
7633
|
signature: 'get(inboxId: string, options?: InboxScope): Promise<TempInboxResource>',
|
|
7634
7634
|
summary: 'Read the lease and counters of a disposable inbox',
|
|
7635
|
-
description: 'Resolves the inbox with its expiry, remaining extensions and message counters, without any messages. To watch an inbox, poll `listMessages` instead: it returns `expiresAt` alongside the mail, so one request covers both.\n\nThis call is authorised by the inbox token, never by an API key. A workspace key sent in its place is refused with 401 `
|
|
7635
|
+
description: 'Resolves the inbox with its expiry, remaining extensions and message counters, without any messages. To watch an inbox, poll `listMessages` instead: it returns `expiresAt` alongside the mail, so one request covers both.\n\nThis call is authorised by the inbox token, never by an API key. A workspace key sent in its place is refused with 401 `missing_inbox_token`, and no scope on any key reaches this resource. The token alone decides which inbox is read, and the id in the path is not checked against it.\n\nOnce the lease is over the token answers 401 `inbox_expired`, and a token this server did not sign answers 404 `resource_not_found`. Deleting an inbox does not end its lease, so its token still reads it. `messageCount` is counted by reading every page, and is 0 when the install cannot read the mailbox that runs the pool.',
|
|
7636
7636
|
method: 'GET',
|
|
7637
7637
|
path: '/temp-mail/inboxes/{id}',
|
|
7638
7638
|
scopes: [],
|
|
7639
7639
|
auth: 'inboxToken',
|
|
7640
7640
|
parameters: [
|
|
7641
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7641
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7642
7642
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7643
7643
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'The `oe_inbox_` token `create` returned. Overrides the token given to `createTempMail` for this call, and is required on the `OpenEmail` client, which would otherwise send its API key.' }
|
|
7644
7644
|
],
|
|
7645
7645
|
returns: '`TempInboxResource` with `id`, `address`, `domain`, `createdAt`, `expiresAt`, `extensionsLeft`, `messageCount`, `messageLimit` and `lastMessageAt`.',
|
|
7646
7646
|
example: 'const temp = createTempMail()\n\nconst created = await temp.create()\n\nconst inbox = await temp.get(created.id, { inboxToken: created.token })\n\nconsole.log(inbox.expiresAt, inbox.messageCount, inbox.messageLimit)',
|
|
7647
7647
|
notes: [
|
|
7648
|
-
'A token that is
|
|
7648
|
+
'A token that does not start with `oe_inbox_` is 401 `missing_inbox_token`, the same as no token at all. One that starts with it but was not signed by this server is 404 `resource_not_found`.',
|
|
7649
7649
|
'`messageCount` counts every message the inbox is showing, across every page, and goes down when one is deleted. `messageLimit`, which is 50, is the page size of `listMessages`, not a ceiling: nothing past it is dropped.'
|
|
7650
7650
|
]
|
|
7651
7651
|
};
|
|
@@ -7709,23 +7709,23 @@ const TempMailListAttachmentsMethod = {
|
|
|
7709
7709
|
name: 'listAttachments',
|
|
7710
7710
|
accessor: 'openemail.tempMail.listAttachments',
|
|
7711
7711
|
slug: 'temp-mail/listAttachments',
|
|
7712
|
-
signature: 'listAttachments(inboxId: string, messageId: string, options?: InboxScope): Promise<Array<
|
|
7713
|
-
summary: '
|
|
7714
|
-
description: 'Resolves every attachment on a message as a plain array
|
|
7712
|
+
signature: 'listAttachments(inboxId: string, messageId: string, options?: InboxScope): Promise<Array<TempAttachmentResource>>',
|
|
7713
|
+
summary: 'List the attachments of a message, metadata only',
|
|
7714
|
+
description: 'Resolves every attachment on a message as a plain array of metadata: `attachmentId`, `filename`, `mimeType` and `size` in decoded bytes, with `body` an empty string and `headers` empty. No route on a disposable inbox serves the bytes, and there is no per attachment fetch.\n\n`filename` and `mimeType` are whatever the sender declared, and nothing here is scanned. A message this lease cannot see is 404 `resource_not_found`, and an install with no key to read the pool answers 503 `not_configured`.',
|
|
7715
7715
|
method: 'GET',
|
|
7716
7716
|
path: '/temp-mail/inboxes/{id}/messages/{messageId}/attachments',
|
|
7717
7717
|
scopes: [],
|
|
7718
7718
|
auth: 'inboxToken',
|
|
7719
7719
|
parameters: [
|
|
7720
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7721
|
-
{ name: 'messageId', type: 'string', required: true, description: '
|
|
7720
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7721
|
+
{ name: 'messageId', type: 'string', required: true, description: 'An `id` from `listMessages`, such as `thr_` and 24 hex.' },
|
|
7722
7722
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
7723
7723
|
{ name: 'options.inboxToken', type: 'string', required: false, description: 'The `oe_inbox_` token `create` returned. Overrides the token given to `createTempMail` for this call, and is required on the `OpenEmail` client, which would otherwise send its API key.' }
|
|
7724
7724
|
],
|
|
7725
|
-
returns: '`Array<
|
|
7726
|
-
example: 'const temp = createTempMail({ inboxToken: \'oe_inbox_Vb3kT9qLm2Xw7RzN4pYc6HfJ1sGa5Ed8KuQo0iWnS2e\' })\n\nconst attachments = await temp.listAttachments(\'
|
|
7725
|
+
returns: '`Array<TempAttachmentResource>`, each with `attachmentId`, `filename`, `mimeType`, `size` in decoded bytes, `body` as an empty string and `headers` as an empty array.',
|
|
7726
|
+
example: 'const temp = createTempMail({ inboxToken: \'oe_inbox_Vb3kT9qLm2Xw7RzN4pYc6HfJ1sGa5Ed8KuQo0iWnS2e\' })\n\nconst attachments = await temp.listAttachments(\'tinb_k7m2q9xw4bdp\', \'thr_9e3b7c1a5f2d8e40b6a9c3f1\')\n\nfor (const file of attachments) console.log(file.filename, file.size)',
|
|
7727
7727
|
notes: [
|
|
7728
|
-
'
|
|
7728
|
+
'Listing attachments does not mark the message seen.',
|
|
7729
7729
|
'Check `attachmentCount` from `listMessages` first to skip this call for messages with none.'
|
|
7730
7730
|
]
|
|
7731
7731
|
};
|
|
@@ -7737,7 +7737,7 @@ const TempMailListDomainsMethod = {
|
|
|
7737
7737
|
slug: 'temp-mail/listDomains',
|
|
7738
7738
|
signature: 'listDomains(options?: InboxScope): Promise<Array<TempDomainResource>>',
|
|
7739
7739
|
summary: 'List the domains a disposable inbox can be created on',
|
|
7740
|
-
description: 'Resolves the pool of domains `create` accepts, as a plain array in the order
|
|
7740
|
+
description: 'Resolves the pool of domains `create` accepts, as a plain array in the order the operator wrote them in `TEMP_MAIL_DOMAINS`. It takes no credential at all: no API key and no inbox token are sent, even when the client holding this namespace has one.\n\nNothing checks that a listed domain is verified, so the list is only as good as the operator made it. An empty array is a normal answer meaning this install offers no disposable domains, and it is exactly the condition under which `create` fails with 503 `not_configured`.',
|
|
7741
7741
|
method: 'GET',
|
|
7742
7742
|
path: '/temp-mail/domains',
|
|
7743
7743
|
scopes: [],
|
|
@@ -7749,7 +7749,7 @@ const TempMailListDomainsMethod = {
|
|
|
7749
7749
|
returns: '`Array<TempDomainResource>`, each with `object` set to `temp_domain` and a lowercased `domain` to pass back to `create`.',
|
|
7750
7750
|
example: 'const temp = createTempMail()\n\nconst domains = await temp.listDomains()\n\nconsole.log(domains.map((entry) => entry.domain))',
|
|
7751
7751
|
notes: [
|
|
7752
|
-
'The
|
|
7752
|
+
'The pool is server configuration, so a domain appears when the operator adds it to `TEMP_MAIL_DOMAINS`, not when it is added to a workspace.',
|
|
7753
7753
|
'Retried automatically on network failure and retryable statuses, like every GET.'
|
|
7754
7754
|
]
|
|
7755
7755
|
};
|
|
@@ -7761,13 +7761,13 @@ const TempMailListMessagesMethod = {
|
|
|
7761
7761
|
slug: 'temp-mail/listMessages',
|
|
7762
7762
|
signature: 'listMessages(inboxId: string, options?: TempMessageListOptions): Promise<TempMessagesResource>',
|
|
7763
7763
|
summary: 'List one page of the messages in a disposable inbox',
|
|
7764
|
-
description: 'Resolves one page of the messages in the inbox newest first, by the time the server received them, together with the inbox `expiresAt`. Rows carry metadata only, and
|
|
7764
|
+
description: 'Resolves one page of the messages in the inbox newest first, by the time the server received them, together with the inbox `expiresAt`. Rows carry metadata only, and polling this is the way to wait for a confirmation email. Only mail delivered to this address since the lease began is listed.\n\nA page holds up to 50 messages. When more have arrived, `hasMore` is true and `nextCursor` goes back as `options.cursor` for the next page, so nothing that reached the inbox is hidden; `listAllMessages` and `iterateMessages` do that walk for you. A page can hold fewer than `limit` rows, even none, while `hasMore` is true, because mail to other addresses on the pool is read and dropped. `snippet` is plain text capped at 400 characters, which is often enough to read a one time code without opening the message.\n\n`spam` is a flag, never a filing decision. A machine sent confirmation from a sender with no reputation is exactly what a disposable inbox exists to receive, so flagged messages are still listed. `from` is whatever the message claimed and has not been authenticated.',
|
|
7765
7765
|
method: 'GET',
|
|
7766
7766
|
path: '/temp-mail/inboxes/{id}/messages',
|
|
7767
7767
|
scopes: [],
|
|
7768
7768
|
auth: 'inboxToken',
|
|
7769
7769
|
parameters: [
|
|
7770
|
-
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It
|
|
7770
|
+
{ name: 'inboxId', type: 'string', required: true, description: 'The `tinb_` id `create` returned. It is not checked against the token, which alone decides the inbox.' },
|
|
7771
7771
|
{ name: 'options.limit', type: 'number', required: false, description: 'Messages per page, a whole number from 1 to 50, defaulting to 50. Out of range is 422 `invalid_parameter`.' },
|
|
7772
7772
|
{ name: 'options.cursor', type: 'string', required: false, description: 'The `nextCursor` from the previous page. Never build one yourself.' },
|
|
7773
7773
|
{ name: 'options.signal', type: 'AbortSignal', required: false, description: 'Cancels the request.' },
|
|
@@ -7779,7 +7779,8 @@ const TempMailListMessagesMethod = {
|
|
|
7779
7779
|
notes: [
|
|
7780
7780
|
'A message whose `Message-ID` header matches one already in the inbox is not stored twice.',
|
|
7781
7781
|
'`to` is the inbox address. A `+tag` the sender added is folded back into the base address.',
|
|
7782
|
-
'Every message the inbox has received is listed, a page at a time. `messageLimit` on the inbox is the size the tool is built for, not a point past which mail is hidden.'
|
|
7782
|
+
'Every message the inbox has received is listed, a page at a time. `messageLimit` on the inbox is the size the tool is built for, not a point past which mail is hidden.',
|
|
7783
|
+
'An install with no key to read the pool answers 503 `not_configured`.'
|
|
7783
7784
|
]
|
|
7784
7785
|
};
|
|
7785
7786
|
|
|
@@ -13662,6 +13663,7 @@ const RESOURCE_ONE_TIME_SECRETS = {
|
|
|
13662
13663
|
const RESOURCE_TEMP_INBOX = {
|
|
13663
13664
|
NAMESPACE: 'tempMail',
|
|
13664
13665
|
CREATE: 'create',
|
|
13666
|
+
EXTEND: 'extend',
|
|
13665
13667
|
DELETE: 'delete',
|
|
13666
13668
|
TOKEN_FIELD: 'token'
|
|
13667
13669
|
};
|
|
@@ -13819,6 +13821,12 @@ const RESOURCE_TYPE_COLUMNS = {
|
|
|
13819
13821
|
TempMessagesResource: TEMP_MESSAGE_COLUMNS,
|
|
13820
13822
|
TempMessageResource: TEMP_MESSAGE_COLUMNS,
|
|
13821
13823
|
TempDomainResource: [Column('domain', 'domain')],
|
|
13824
|
+
TempAttachmentResource: [
|
|
13825
|
+
Column('id', 'attachmentId', RENDER_CELL_FORMATS.ID),
|
|
13826
|
+
Column('filename', 'filename'),
|
|
13827
|
+
Column('type', 'mimeType'),
|
|
13828
|
+
Column('size', 'size', RENDER_CELL_FORMATS.BYTES)
|
|
13829
|
+
],
|
|
13822
13830
|
ApiKeyRequestResource: [
|
|
13823
13831
|
Column('id', 'id', RENDER_CELL_FORMATS.ID),
|
|
13824
13832
|
Column('method', 'method'),
|
|
@@ -13978,7 +13986,7 @@ const RESOURCE_HELP = {
|
|
|
13978
13986
|
NOTES: 'NOTES',
|
|
13979
13987
|
NO_SCOPES: 'Any key or sign-in for the workspace.',
|
|
13980
13988
|
SIGNED_OUT: 'None. It works signed out, with no key.',
|
|
13981
|
-
INBOX_TOKEN: 'None. The inbox token authorises it: `--inbox-token`, or the token the CLI saved when it created the inbox.',
|
|
13989
|
+
INBOX_TOKEN: 'None. The inbox token authorises it: `--inbox-token`, or the token the CLI saved when it created or last extended the inbox.',
|
|
13982
13990
|
REQUIRED: 'Required.',
|
|
13983
13991
|
REQUIRED_WITH_DATA: 'Required, here or in `--data`.',
|
|
13984
13992
|
PAGING: 'Add `--all` to walk every page: a table on a terminal, one JSON object per line when piped or with `--ndjson`, and one `{ items, hasMore, nextCursor }` document with `--json`. `--max <n>` stops after that many items.',
|
|
@@ -14008,6 +14016,7 @@ const RESOURCE_MESSAGES = {
|
|
|
14008
14016
|
INBOX_TOKEN_REJECTED: 'The inbox token was refused.',
|
|
14009
14017
|
INBOX_TOKEN_REJECTED_NEXT: 'Check `--inbox-token`, or create a new inbox with `openemail temp new`.',
|
|
14010
14018
|
INBOX_SAVED: (address) => `Saved the inbox token for ${address}, so later temp-mail commands find it.`,
|
|
14019
|
+
INBOX_RENEWED: (address) => `Saved the new inbox token for ${address}, which carries the later expiry, so later temp-mail commands use it.`,
|
|
14011
14020
|
SECRET_IN_ARGV: (label) => `${label} was passed on the command line, where your shell history and the process list can show it. Next time pass \`-\` to read it from standard input, or \`@path\` to read it from a file.`,
|
|
14012
14021
|
NO_KEYLESS_RESOURCE: (namespace) => `The ${namespace} commands need a credential.`,
|
|
14013
14022
|
NO_METHOD: (accessor) => `${accessor} is not in the SDK this CLI was built with.`,
|
|
@@ -14108,13 +14117,13 @@ const TEMP_FLAGS = {
|
|
|
14108
14117
|
name: TEMP_FLAG_NAMES.DOMAIN,
|
|
14109
14118
|
kind: FLAG_KINDS.STRING,
|
|
14110
14119
|
placeholder: 'domain',
|
|
14111
|
-
description: 'Domain from `openemail temp-mail list-domains`. Left out, one
|
|
14120
|
+
description: 'Domain from `openemail temp-mail list-domains`. Left out, the first one in that list is used'
|
|
14112
14121
|
},
|
|
14113
14122
|
NAME: {
|
|
14114
14123
|
name: TEMP_FLAG_NAMES.NAME,
|
|
14115
14124
|
kind: FLAG_KINDS.STRING,
|
|
14116
14125
|
placeholder: 'local-part',
|
|
14117
|
-
description: 'The part before the @,
|
|
14126
|
+
description: 'The part before the @, up to 64 letters, digits, dots, dashes or underscores, starting and ending with a letter or digit. Left out, one is generated. Anyone who asks for the same name reads its mail too'
|
|
14118
14127
|
},
|
|
14119
14128
|
TTL: {
|
|
14120
14129
|
name: TEMP_FLAG_NAMES.TTL,
|
|
@@ -14146,7 +14155,10 @@ const TEMP_LIMITS = {
|
|
|
14146
14155
|
SNIPPET_WIDTH: 60
|
|
14147
14156
|
};
|
|
14148
14157
|
const TEMP_TOKEN_PREFIX = 'oe_inbox_';
|
|
14149
|
-
const TEMP_MESSAGE_PREFIX = '
|
|
14158
|
+
const TEMP_MESSAGE_PREFIX = 'thr_';
|
|
14159
|
+
const TEMP_API_ERROR_CODES = {
|
|
14160
|
+
INBOX_EXPIRED: 'inbox_expired'
|
|
14161
|
+
};
|
|
14150
14162
|
const TEMP_COLUMNS = {
|
|
14151
14163
|
ID: 'Inbox',
|
|
14152
14164
|
ADDRESS: 'Address',
|
|
@@ -14161,7 +14173,7 @@ const TEMP_MESSAGES = {
|
|
|
14161
14173
|
CREATING: 'Creating a temporary inbox',
|
|
14162
14174
|
LOADING: 'Loading messages',
|
|
14163
14175
|
LOADING_MESSAGE: 'Loading the message',
|
|
14164
|
-
DELETING: '
|
|
14176
|
+
DELETING: 'Moving the mail to the bin',
|
|
14165
14177
|
NO_INBOXES: 'You have no temporary inboxes.',
|
|
14166
14178
|
NO_INBOXES_NEXT: 'Run `openemail temp new` to create one.',
|
|
14167
14179
|
PICK_INBOX: 'Which inbox?',
|
|
@@ -14170,12 +14182,9 @@ const TEMP_MESSAGES = {
|
|
|
14170
14182
|
TTL_RANGE: '--ttl takes a whole number of minutes from 1 to 1440.',
|
|
14171
14183
|
INVALID_TOKEN: '--inbox-token must be an inbox token beginning oe_inbox_.',
|
|
14172
14184
|
NO_MESSAGES: 'No mail yet.',
|
|
14173
|
-
WATCH_STOPPED: 'Stopped watching.',
|
|
14174
14185
|
EXPIRED: 'The inbox has expired, so there is nothing more to watch.',
|
|
14175
|
-
GONE_LOCALLY: 'The
|
|
14176
|
-
|
|
14177
|
-
EMPTY_BODY: '(This message has no body.)',
|
|
14178
|
-
UNVERIFIED_SENDER: 'The sender of mail in a temporary inbox is not verified.',
|
|
14186
|
+
GONE_LOCALLY: 'The server no longer accepts this inbox token, so nothing was moved and the inbox was only forgotten here.',
|
|
14187
|
+
LEASE_RUNS_ON: 'The lease runs on until it expires, so mail can still arrive, and nothing holds the address back from anybody else.',
|
|
14179
14188
|
MORE: (id) => `Older messages are not shown here. Run \`openemail temp-mail list-messages ${id} --all\` to see every one.`,
|
|
14180
14189
|
ALREADY_HERE: 'Already in the inbox:',
|
|
14181
14190
|
CREATED: (address) => `Created ${address}.`,
|
|
@@ -14185,8 +14194,8 @@ const TEMP_MESSAGES = {
|
|
|
14185
14194
|
PRUNED: (count) => `Forgot ${count} expired ${count === 1 ? 'inbox' : 'inboxes'}.`,
|
|
14186
14195
|
WATCHING: (address) => `Watching ${address}`,
|
|
14187
14196
|
WAITING: (left) => `Waiting for mail, ${left} left. Press Ctrl+C to stop`,
|
|
14188
|
-
CONFIRM_DELETE: (address) => `
|
|
14189
|
-
DELETED: (address) => `
|
|
14197
|
+
CONFIRM_DELETE: (address) => `Move every message in ${address} to the bin and forget the inbox here?`,
|
|
14198
|
+
DELETED: (address) => `Moved the mail in ${address} to the bin and forgot the inbox here.`,
|
|
14190
14199
|
SENDER_HINT: (id) => `Run \`openemail temp read ${id} <message-id>\` to read one.`
|
|
14191
14200
|
};
|
|
14192
14201
|
|
|
@@ -18420,6 +18429,17 @@ const Find$1 = (idOrAddress) => {
|
|
|
18420
18429
|
return List$2().find(inbox => inbox.id === idOrAddress || inbox.address.toLowerCase() === needle) ?? null;
|
|
18421
18430
|
};
|
|
18422
18431
|
const Add = (inbox) => Change(inboxes => [...inboxes.filter(existing => existing.id !== inbox.id), inbox]);
|
|
18432
|
+
const Renew$1 = (id, inboxToken, expiresAt) => {
|
|
18433
|
+
const outcome = { renewed: false };
|
|
18434
|
+
Change(inboxes => {
|
|
18435
|
+
const stored = inboxes.find(inbox => inbox.id === id);
|
|
18436
|
+
if (stored === undefined || Date.parse(expiresAt) < Date.parse(stored.expiresAt))
|
|
18437
|
+
return null;
|
|
18438
|
+
outcome.renewed = true;
|
|
18439
|
+
return inboxes.map(inbox => inbox === stored ? { ...inbox, inboxToken, expiresAt } : inbox);
|
|
18440
|
+
});
|
|
18441
|
+
return outcome.renewed;
|
|
18442
|
+
};
|
|
18423
18443
|
const Remove$1 = (id) => {
|
|
18424
18444
|
const outcome = { removed: false };
|
|
18425
18445
|
Change(inboxes => {
|
|
@@ -18443,6 +18463,7 @@ const TempMailStore = {
|
|
|
18443
18463
|
List: List$2,
|
|
18444
18464
|
Find: Find$1,
|
|
18445
18465
|
Add,
|
|
18466
|
+
Renew: Renew$1,
|
|
18446
18467
|
Remove: Remove$1,
|
|
18447
18468
|
PruneExpired
|
|
18448
18469
|
};
|
|
@@ -25637,6 +25658,8 @@ const Send = {
|
|
|
25637
25658
|
}
|
|
25638
25659
|
};
|
|
25639
25660
|
|
|
25661
|
+
const InboxGone = (error) => error instanceof OpenEmailApiError && (error.isNotFound || error.code === TEMP_API_ERROR_CODES.INBOX_EXPIRED);
|
|
25662
|
+
|
|
25640
25663
|
const FromStored = (inbox) => ({
|
|
25641
25664
|
id: inbox.id,
|
|
25642
25665
|
address: inbox.address,
|
|
@@ -25703,15 +25726,15 @@ const TempClient = (baseUrl, signal) => createTempMail(SdkBaseOptions(baseUrl, s
|
|
|
25703
25726
|
const TempDelete = {
|
|
25704
25727
|
name: TEMP_COMMAND_NAMES.DELETE,
|
|
25705
25728
|
aliases: ['rm'],
|
|
25706
|
-
summary: '
|
|
25729
|
+
summary: 'Move the mail in a disposable inbox to the bin and forget its token',
|
|
25707
25730
|
usage: ['temp delete [inbox] [--yes]'],
|
|
25708
|
-
description: '
|
|
25731
|
+
description: 'Moves every message the inbox shows to the bin at once, then removes the inbox and its token from `~/.openemail/temp-mail.json`. Nothing about the lease is stored on the server, so there is nothing to revoke: it runs on until its expiry, and mail that arrives in the meantime still reaches whoever holds the token. Nothing holds the address back either, so it can be issued again at once, to anybody. You are asked to confirm unless you pass `--yes`.',
|
|
25709
25732
|
arguments: [{ name: 'inbox', description: 'Inbox id or address from `openemail temp list`, optional when you have only one' }],
|
|
25710
25733
|
flags: [TEMP_FLAGS.INBOX_TOKEN],
|
|
25711
25734
|
examples: [
|
|
25712
25735
|
{ command: 'openemail temp delete' },
|
|
25713
25736
|
{ command: 'openemail temp rm quiet-otter-12@example-temp.com --yes' },
|
|
25714
|
-
{ command: 'openemail temp delete
|
|
25737
|
+
{ command: 'openemail temp delete tinb_k7m2q9xw4bdp --yes --json' }
|
|
25715
25738
|
],
|
|
25716
25739
|
auth: COMMAND_AUTHS.NONE,
|
|
25717
25740
|
destructive: true,
|
|
@@ -25722,7 +25745,7 @@ const TempDelete = {
|
|
|
25722
25745
|
if (!await ConfirmDestructive(TEMP_MESSAGES.CONFIRM_DELETE(address)))
|
|
25723
25746
|
return EXIT_CODES.CANCELLED;
|
|
25724
25747
|
const gone = await CallAnonymous(context, TEMP_MESSAGES.DELETING, signal => TempClient(baseUrl, signal).delete(target.id, { inboxToken: target.token, signal })).then(() => false, (error) => {
|
|
25725
|
-
if (error
|
|
25748
|
+
if (InboxGone(error))
|
|
25726
25749
|
return true;
|
|
25727
25750
|
throw error;
|
|
25728
25751
|
});
|
|
@@ -25731,10 +25754,12 @@ const TempDelete = {
|
|
|
25731
25754
|
context.io.Json({ object: 'temp_inbox', id: target.id, address: target.address, destroyed: !gone, forgotten });
|
|
25732
25755
|
return EXIT_CODES.OK;
|
|
25733
25756
|
}
|
|
25734
|
-
if (gone)
|
|
25757
|
+
if (gone) {
|
|
25735
25758
|
context.io.Warn(TEMP_MESSAGES.GONE_LOCALLY);
|
|
25736
|
-
|
|
25737
|
-
|
|
25759
|
+
return EXIT_CODES.OK;
|
|
25760
|
+
}
|
|
25761
|
+
context.io.Success(TEMP_MESSAGES.DELETED(address));
|
|
25762
|
+
context.io.Hint(TEMP_MESSAGES.LEASE_RUNS_ON);
|
|
25738
25763
|
return EXIT_CODES.OK;
|
|
25739
25764
|
}
|
|
25740
25765
|
};
|
|
@@ -25743,7 +25768,7 @@ const TempList = {
|
|
|
25743
25768
|
name: TEMP_COMMAND_NAMES.LIST,
|
|
25744
25769
|
summary: 'List the disposable inboxes this CLI created',
|
|
25745
25770
|
usage: ['temp list'],
|
|
25746
|
-
description: 'Lists the inboxes kept in `~/.openemail/temp-mail.json`, soonest to expire last. Expired inboxes are forgotten as they are found, because
|
|
25771
|
+
description: 'Lists the inboxes kept in `~/.openemail/temp-mail.json`, soonest to expire last. Expired inboxes are forgotten as they are found, because their tokens no longer open anything. An inbox extended with `openemail temp-mail extend` shows its later expiry. Nothing is read from the network.',
|
|
25747
25772
|
examples: [
|
|
25748
25773
|
{ command: 'openemail temp list' },
|
|
25749
25774
|
{ command: 'openemail temp ls --json', note: 'Ids, addresses and expiry times, never the tokens' }
|
|
@@ -25790,7 +25815,7 @@ const TempNew = {
|
|
|
25790
25815
|
aliases: ['create'],
|
|
25791
25816
|
summary: 'Create a disposable inbox',
|
|
25792
25817
|
usage: ['temp new [--name <local-part>] [--domain <domain>] [--ttl <minutes>]'],
|
|
25793
|
-
description: 'Creates a disposable address that needs no account and no API key, and keeps its token in `~/.openemail/temp-mail.json` so the other `temp` commands can reach it. The address is printed on stdout on its own, so `ADDRESS=$(openemail temp new)` works in a script. It lives for 60 minutes unless you pass `--ttl`, up to 24 hours.',
|
|
25818
|
+
description: 'Creates a disposable address that needs no account and no API key, and keeps its token in `~/.openemail/temp-mail.json` so the other `temp` commands can reach it. The address is printed on stdout on its own, so `ADDRESS=$(openemail temp new)` works in a script. It lives for 60 minutes unless you pass `--ttl`, up to 24 hours, and `openemail temp-mail extend` adds up to an hour at a time within 24 hours of its creation, saving the new token it returns. A name you choose with `--name` is issued to anyone who asks for it too, so leave it out when the mail should reach you alone.',
|
|
25794
25819
|
flags: [TEMP_FLAGS.NAME, TEMP_FLAGS.DOMAIN, TEMP_FLAGS.TTL],
|
|
25795
25820
|
examples: [
|
|
25796
25821
|
{ command: 'openemail temp new' },
|
|
@@ -25827,7 +25852,7 @@ const TempNew = {
|
|
|
25827
25852
|
}
|
|
25828
25853
|
};
|
|
25829
25854
|
|
|
25830
|
-
const PrintDetail = (
|
|
25855
|
+
const PrintDetail = (detail, raw) => {
|
|
25831
25856
|
const view = MessageView(detail.message);
|
|
25832
25857
|
PrintMailMessage({
|
|
25833
25858
|
...view,
|
|
@@ -25837,8 +25862,6 @@ const PrintDetail = (context, detail, raw) => {
|
|
|
25837
25862
|
subject: view.subject || detail.subject,
|
|
25838
25863
|
date: view.date ?? detail.receivedAt
|
|
25839
25864
|
}, raw);
|
|
25840
|
-
if (detail.truncated)
|
|
25841
|
-
context.io.Warn(TEMP_MESSAGES.TRUNCATED);
|
|
25842
25865
|
};
|
|
25843
25866
|
const ListMessages = async (context, target) => {
|
|
25844
25867
|
const baseUrl = TempBaseUrl(context, target);
|
|
@@ -25877,22 +25900,23 @@ const TempRead = {
|
|
|
25877
25900
|
aliases: ['show', 'open'],
|
|
25878
25901
|
summary: 'List the mail in a disposable inbox, or read one message',
|
|
25879
25902
|
usage: ['temp read [inbox] [message-id] [flags]'],
|
|
25880
|
-
description: 'Without a message id it lists what has arrived, newest first, with a short preview that is often enough to read a one time code. With one it prints the whole message, turning HTML into readable text. The inbox is its id or address, and can be left out when you have only one. Reading a message
|
|
25903
|
+
description: 'Without a message id it lists what has arrived, newest first, with a short preview that is often enough to read a one time code. With one it prints the whole message, turning HTML into readable text, and lists its attachments by name and size, without their bytes. The inbox is its id or address, and can be left out when you have only one, so a lone `thr_` id reads that message. Reading a message does not mark it seen, and its body is never cut. The sender of mail in a temporary inbox is never verified.',
|
|
25881
25904
|
arguments: [
|
|
25882
25905
|
{ name: 'inbox', description: 'Inbox id or address from `openemail temp list`' },
|
|
25883
|
-
{ name: 'message-id', description: 'A `
|
|
25906
|
+
{ name: 'message-id', description: 'A `thr_` id from the list' }
|
|
25884
25907
|
],
|
|
25885
25908
|
flags: [TEMP_FLAGS.HTML, TEMP_FLAGS.INBOX_TOKEN],
|
|
25886
25909
|
examples: [
|
|
25887
25910
|
{ command: 'openemail temp read' },
|
|
25888
25911
|
{ command: 'openemail temp read quiet-otter-12@example-temp.com' },
|
|
25889
|
-
{ command: 'openemail temp read
|
|
25890
|
-
{ command: 'openemail temp read
|
|
25912
|
+
{ command: 'openemail temp read thr_9e3b7c1a5f2d8e40b6a9c3f1', note: 'Reads one message from your only inbox' },
|
|
25913
|
+
{ command: 'openemail temp read tinb_k7m2q9xw4bdp thr_9e3b7c1a5f2d8e40b6a9c3f1' },
|
|
25914
|
+
{ command: 'openemail temp read tinb_k7m2q9xw4bdp thr_9e3b7c1a5f2d8e40b6a9c3f1 --html > message.html' }
|
|
25891
25915
|
],
|
|
25892
25916
|
auth: COMMAND_AUTHS.NONE,
|
|
25893
25917
|
run: async (context) => {
|
|
25894
25918
|
const [first, second] = context.args.positional;
|
|
25895
|
-
const firstIsMessage = first !== undefined && second === undefined && first.startsWith(TEMP_MESSAGE_PREFIX);
|
|
25919
|
+
const firstIsMessage = first !== undefined && second === undefined && first.startsWith(TEMP_MESSAGE_PREFIX) && !first.includes('@');
|
|
25896
25920
|
const target = await ResolveTempInbox(context, firstIsMessage ? null : first ?? null);
|
|
25897
25921
|
const messageId = firstIsMessage ? first : second;
|
|
25898
25922
|
if (messageId === undefined)
|
|
@@ -25902,7 +25926,7 @@ const TempRead = {
|
|
|
25902
25926
|
if (context.io.json)
|
|
25903
25927
|
context.io.Json(detail);
|
|
25904
25928
|
else
|
|
25905
|
-
PrintDetail(
|
|
25929
|
+
PrintDetail(detail, Flags$2.Bool(context.args.flags, TEMP_FLAG_NAMES.HTML));
|
|
25906
25930
|
return EXIT_CODES.OK;
|
|
25907
25931
|
}
|
|
25908
25932
|
};
|
|
@@ -25931,11 +25955,18 @@ const Print = (context, messages) => {
|
|
|
25931
25955
|
};
|
|
25932
25956
|
const Left = (expiresAt) => RelativeTime(expiresAt).replace(/^in /, '');
|
|
25933
25957
|
const Waiting = (context, expiresAt) => context.io.Spinner(TEMP_MESSAGES.WAITING(Left(expiresAt)));
|
|
25958
|
+
const Extended = (target, expiresAt) => {
|
|
25959
|
+
if (!target.fromStore)
|
|
25960
|
+
return null;
|
|
25961
|
+
const stored = TempMailStore.Find(target.id);
|
|
25962
|
+
const later = Date.parse(stored?.expiresAt ?? '');
|
|
25963
|
+
return stored !== null && !Number.isNaN(later) && (Number.isNaN(expiresAt) || later > expiresAt) ? stored : null;
|
|
25964
|
+
};
|
|
25934
25965
|
const TempWatch = {
|
|
25935
25966
|
name: TEMP_COMMAND_NAMES.WATCH,
|
|
25936
25967
|
summary: 'Wait for mail to arrive in a disposable inbox',
|
|
25937
25968
|
usage: ['temp watch [inbox] [--first]'],
|
|
25938
|
-
description: 'Prints what is already in the inbox, then checks every 3 seconds and prints each new message as it lands, until you press Ctrl+C or the inbox expires. With `--first` it stops as soon as there is a message, which suits a script waiting for a sign-up code. With `--json` each message is one JSON line on stdout.',
|
|
25969
|
+
description: 'Prints what is already in the inbox, then checks every 3 seconds and prints each new message as it lands, until you press Ctrl+C or the inbox expires. When `openemail temp-mail extend` saves a new token for the inbox meanwhile, the watch carries on with it until the later expiry. With `--first` it stops as soon as there is a message, which suits a script waiting for a sign-up code. With `--json` each message is one JSON line on stdout.',
|
|
25939
25970
|
arguments: [{ name: 'inbox', description: 'Inbox id or address from `openemail temp list`, optional when you have only one' }],
|
|
25940
25971
|
flags: [TEMP_FLAGS.FIRST, TEMP_FLAGS.INBOX_TOKEN],
|
|
25941
25972
|
examples: [
|
|
@@ -25948,7 +25979,8 @@ const TempWatch = {
|
|
|
25948
25979
|
const target = await ResolveTempInbox(context, context.args.positional[0] ?? null);
|
|
25949
25980
|
const first = Flags$2.Bool(context.args.flags, TEMP_FLAG_NAMES.FIRST);
|
|
25950
25981
|
const baseUrl = TempBaseUrl(context, target);
|
|
25951
|
-
const
|
|
25982
|
+
const lease = { token: target.token };
|
|
25983
|
+
const List = () => CallAnonymous(context, TEMP_MESSAGES.LOADING, signal => TempClient(baseUrl, signal).listMessages(target.id, { inboxToken: lease.token, signal }), { spinner: false });
|
|
25952
25984
|
const opening = await List();
|
|
25953
25985
|
const seen = new Set(opening.items.map(message => message.id));
|
|
25954
25986
|
let expiresAt = Date.parse(opening.expiresAt ?? target.expiresAt ?? '');
|
|
@@ -25965,6 +25997,12 @@ const TempWatch = {
|
|
|
25965
25997
|
const spinner = Waiting(context, expiresAt);
|
|
25966
25998
|
try {
|
|
25967
25999
|
for (;;) {
|
|
26000
|
+
const extended = Extended(target, expiresAt);
|
|
26001
|
+
if (extended !== null) {
|
|
26002
|
+
lease.token = extended.inboxToken;
|
|
26003
|
+
expiresAt = Date.parse(extended.expiresAt);
|
|
26004
|
+
spinner.Update(TEMP_MESSAGES.WAITING(Left(expiresAt)));
|
|
26005
|
+
}
|
|
25968
26006
|
if (!Number.isNaN(expiresAt) && Date.now() >= expiresAt) {
|
|
25969
26007
|
spinner.Stop();
|
|
25970
26008
|
context.io.Info(TEMP_MESSAGES.EXPIRED);
|
|
@@ -25972,10 +26010,12 @@ const TempWatch = {
|
|
|
25972
26010
|
}
|
|
25973
26011
|
await Sleep$2(TEMP_LIMITS.WATCH_INTERVAL_MS, context.signal);
|
|
25974
26012
|
const page = await List().catch((error) => {
|
|
25975
|
-
if (error
|
|
26013
|
+
if (InboxGone(error))
|
|
25976
26014
|
return null;
|
|
25977
26015
|
throw error;
|
|
25978
26016
|
});
|
|
26017
|
+
if (page === null && Extended(target, expiresAt) !== null)
|
|
26018
|
+
continue;
|
|
25979
26019
|
if (page === null) {
|
|
25980
26020
|
spinner.Stop();
|
|
25981
26021
|
context.io.Info(TEMP_MESSAGES.EXPIRED);
|
|
@@ -26003,12 +26043,12 @@ const Temp = {
|
|
|
26003
26043
|
name: TEMP_COMMAND_NAMES.TEMP,
|
|
26004
26044
|
summary: 'Disposable inboxes for sign-ups and tests',
|
|
26005
26045
|
usage: ['temp <new|list|read|watch|delete> [arguments] [flags]'],
|
|
26006
|
-
description: 'Creates short-lived addresses that anyone can use without signing in, and reads the mail they receive.
|
|
26046
|
+
description: 'Creates short-lived addresses that anyone can use without signing in, and reads the mail they receive. The inbox token is the lease itself and nothing about it is stored on the server, so it is kept in `~/.openemail/temp-mail.json` (mode 0600) and later commands find the inbox by its id or address. A lost token cannot be recovered. Mail that lands in a disposable inbox comes from strangers and its sender is never verified.',
|
|
26007
26047
|
subcommands: [TempNew, TempList, TempRead, TempWatch, TempDelete],
|
|
26008
26048
|
examples: [
|
|
26009
26049
|
{ command: 'openemail temp new', note: 'Prints a fresh address' },
|
|
26010
26050
|
{ command: 'openemail temp watch', note: 'Waits for mail and prints each message as it lands' },
|
|
26011
|
-
{ command: 'openemail temp read
|
|
26051
|
+
{ command: 'openemail temp read thr_9e3b7c1a5f2d8e40b6a9c3f1', note: 'Reads one message by its thread id' },
|
|
26012
26052
|
{ command: 'openemail temp delete --yes' }
|
|
26013
26053
|
],
|
|
26014
26054
|
section: HELP_SECTIONS.MAIL,
|
|
@@ -26335,6 +26375,13 @@ const RememberRotatedKey = (context, verb, call, value) => {
|
|
|
26335
26375
|
};
|
|
26336
26376
|
|
|
26337
26377
|
const Text = (value) => typeof value === 'string' && value.length > 0 ? value : null;
|
|
26378
|
+
const Renew = (id, address, token, expiresAt, baseUrl) => {
|
|
26379
|
+
const stored = TempMailStore.Find(id);
|
|
26380
|
+
if (stored === null || OriginOf(stored.baseUrl ?? DEFAULT_API_URL) !== OriginOf(baseUrl))
|
|
26381
|
+
return;
|
|
26382
|
+
if (TempMailStore.Renew(id, token, expiresAt))
|
|
26383
|
+
Console.Info(RESOURCE_MESSAGES.INBOX_RENEWED(Color.Sanitize(address)));
|
|
26384
|
+
};
|
|
26338
26385
|
const RememberTempInbox = (verb, call, value, baseUrl) => {
|
|
26339
26386
|
if (verb.namespace !== RESOURCE_TEMP_INBOX.NAMESPACE)
|
|
26340
26387
|
return;
|
|
@@ -26344,7 +26391,9 @@ const RememberTempInbox = (verb, call, value, baseUrl) => {
|
|
|
26344
26391
|
TempMailStore.Remove(inbox);
|
|
26345
26392
|
return;
|
|
26346
26393
|
}
|
|
26347
|
-
if (call.method !== RESOURCE_TEMP_INBOX.CREATE
|
|
26394
|
+
if (call.method !== RESOURCE_TEMP_INBOX.CREATE && call.method !== RESOURCE_TEMP_INBOX.EXTEND)
|
|
26395
|
+
return;
|
|
26396
|
+
if (!IsPlainObject(value))
|
|
26348
26397
|
return;
|
|
26349
26398
|
const id = Text(value.id);
|
|
26350
26399
|
const address = Text(value.address);
|
|
@@ -26352,6 +26401,10 @@ const RememberTempInbox = (verb, call, value, baseUrl) => {
|
|
|
26352
26401
|
const expiresAt = Text(value.expiresAt);
|
|
26353
26402
|
if (id === null || address === null || token === null || expiresAt === null)
|
|
26354
26403
|
return;
|
|
26404
|
+
if (call.method === RESOURCE_TEMP_INBOX.EXTEND) {
|
|
26405
|
+
Renew(id, address, token, expiresAt, baseUrl);
|
|
26406
|
+
return;
|
|
26407
|
+
}
|
|
26355
26408
|
TempMailStore.Add({ id, address, inboxToken: token, expiresAt, baseUrl });
|
|
26356
26409
|
Console.Info(RESOURCE_MESSAGES.INBOX_SAVED(Color.Sanitize(address)));
|
|
26357
26410
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openemail/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"author": "OpenEmail",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"description": "The official OpenEmail command line. Sign in once, then send and read mail, manage domains, addresses, keys, webhooks, templates, contacts, audiences and every other OpenEmail resource, use the AI tools and connect MCP clients from your terminal.",
|