tossinbox 0.1.4 → 0.1.5
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 +10 -4
- package/dist/cli.js +18 -6
- package/dist/core/failover.js +66 -0
- package/dist/core/index.js +1 -0
- package/dist/mcp.js +12 -5
- package/dist/version.js +1 -1
- package/llms.txt +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ tossinbox wait --code --json
|
|
|
107
107
|
|
|
108
108
|
| Command | Description |
|
|
109
109
|
|---|---|
|
|
110
|
-
| `spawn` | Create a new disposable inbox (`-p provider`, `-l label`) |
|
|
110
|
+
| `spawn` | Create a new disposable inbox (`-p provider`, `-l label`). If the provider is down, another one is used automatically — `--no-failover` opts out |
|
|
111
111
|
| `list` | List messages (`-a address`) |
|
|
112
112
|
| `read <id>` | Read a full message, including any detected code |
|
|
113
113
|
| `wait` | Poll until a message arrives (`-f sender`, `-s subject`, `-c` extract code, `-t timeout` max 600s) |
|
|
@@ -239,6 +239,12 @@ ships no bulk-send or bulk-signup mode.
|
|
|
239
239
|
Yes, anywhere Node.js 18+ runs. `npx tossinbox@latest spawn`
|
|
240
240
|
works in PowerShell exactly the same.
|
|
241
241
|
|
|
242
|
+
**What if a provider is down?**
|
|
243
|
+
`spawn` fails over automatically: it retries the create against the remaining
|
|
244
|
+
providers and reports the switch (human mode prints a `⚠` warning and
|
|
245
|
+
`provider : mailtm (failover from mailgw)`; `--json` returns a `failover`
|
|
246
|
+
object). Use `--no-failover` if you need the chosen provider or nothing.
|
|
247
|
+
|
|
242
248
|
**A site blocked my disposable address. What now?**
|
|
243
249
|
Some sites blocklist known disposable domains. Try the other provider:
|
|
244
250
|
`tossinbox spawn -p guerrillamail`. If both are blocked, the site wins that
|
|
@@ -260,9 +266,9 @@ round.
|
|
|
260
266
|
- [x] Homebrew tap: `brew install mohamed-khairy-5i/tap/tossinbox`
|
|
261
267
|
- [x] Project website at [tossinbox.pages.dev](https://tossinbox.pages.dev/)
|
|
262
268
|
- [x] Publish `tossinbox` + `tossinbox-mcp` to the [npm registry](https://www.npmjs.com/package/tossinbox)
|
|
263
|
-
- [
|
|
264
|
-
- [
|
|
265
|
-
- [
|
|
269
|
+
- [x] `mail.gw` provider (v0.1.3)
|
|
270
|
+
- [x] Four more providers: `tempmail.lol`, `temp-mail.io`, `tempmail.plus`, `maildrop.cc` (v0.1.4)
|
|
271
|
+
- [x] Provider failover: auto-switch when a provider is down (v0.1.5)
|
|
266
272
|
- [ ] Homebrew core formula (after community adoption)
|
|
267
273
|
|
|
268
274
|
## Documentation
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, CommanderError } from "commander";
|
|
3
|
-
import { DEFAULT_PROVIDER, getProvider, listProviders, listSavedInboxes, removeInbox, clearInboxes, resolveInbox, saveInbox, statePath, waitForMessage, sleep, htmlToText, ProviderError, } from "./core/index.js";
|
|
3
|
+
import { DEFAULT_PROVIDER, createInboxWithFailover, getProvider, listProviders, listSavedInboxes, removeInbox, clearInboxes, resolveInbox, saveInbox, statePath, waitForMessage, sleep, htmlToText, ProviderError, } from "./core/index.js";
|
|
4
4
|
import { VERSION } from "./version.js";
|
|
5
5
|
/* Documented exit codes:
|
|
6
6
|
* 0 success
|
|
@@ -78,20 +78,32 @@ function printMessageHuman(message, withBody) {
|
|
|
78
78
|
/* ------------------------------------------------------------------ */
|
|
79
79
|
program
|
|
80
80
|
.command("spawn")
|
|
81
|
-
.description("Create a new disposable inbox")
|
|
81
|
+
.description("Create a new disposable inbox (automatically falls back to another provider when the chosen one is down)")
|
|
82
82
|
.option("-p, --provider <name>", "email provider (see: providers)", DEFAULT_PROVIDER)
|
|
83
83
|
.option("-l, --label <label>", "optional label to identify this inbox")
|
|
84
|
+
.option("--no-failover", "fail if the chosen provider is down instead of falling back to another one")
|
|
84
85
|
.action(async (opts) => {
|
|
85
86
|
try {
|
|
86
|
-
|
|
87
|
-
const inbox = await
|
|
87
|
+
providerOrExit(opts.provider); // unknown name = usage error before any network call
|
|
88
|
+
const { inbox, switched, warnings } = await createInboxWithFailover({
|
|
89
|
+
requested: opts.provider,
|
|
90
|
+
label: opts.label,
|
|
91
|
+
failover: opts.failover,
|
|
92
|
+
});
|
|
88
93
|
await saveInbox(inbox);
|
|
89
94
|
if (jsonMode()) {
|
|
90
|
-
out({
|
|
95
|
+
out({
|
|
96
|
+
ok: true,
|
|
97
|
+
inbox,
|
|
98
|
+
...(switched ? { failover: { requested: opts.provider, used: inbox.provider } } : {}),
|
|
99
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
100
|
+
});
|
|
91
101
|
return;
|
|
92
102
|
}
|
|
103
|
+
for (const warning of warnings)
|
|
104
|
+
console.error(`⚠ ${warning}`);
|
|
93
105
|
console.log(`✔ Inbox ready : ${inbox.address}`);
|
|
94
|
-
console.log(` provider : ${inbox.provider}`);
|
|
106
|
+
console.log(` provider : ${inbox.provider}${switched ? ` (failover from ${opts.provider})` : ""}`);
|
|
95
107
|
if (inbox.label)
|
|
96
108
|
console.log(` label : ${inbox.label}`);
|
|
97
109
|
console.log(` state file : ${statePath()}`);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { providers } from "./index.js";
|
|
2
|
+
import { ProviderError } from "./types.js";
|
|
3
|
+
/** A failure that automatic failover is allowed to recover from: network-level
|
|
4
|
+
* errors (no status) and server-side trouble (5xx) or throttling (429).
|
|
5
|
+
* A plain 4xx is a real request problem — switching providers cannot fix it. */
|
|
6
|
+
function isTransient(err) {
|
|
7
|
+
if (!(err instanceof ProviderError))
|
|
8
|
+
return true; // network-level → transient
|
|
9
|
+
const { status } = err;
|
|
10
|
+
return status === undefined || status === 429 || status >= 500;
|
|
11
|
+
}
|
|
12
|
+
function describe(err) {
|
|
13
|
+
return err instanceof Error ? err.message : String(err);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create an inbox, falling back to other providers when the requested one is
|
|
17
|
+
* down. The requested provider is always tried first; the remaining providers
|
|
18
|
+
* follow registration order (best default first). Every failed attempt is
|
|
19
|
+
* recorded as a warning so humans and agents can see exactly what happened.
|
|
20
|
+
*/
|
|
21
|
+
export async function createInboxWithFailover(options = {}) {
|
|
22
|
+
const failover = options.failover !== false;
|
|
23
|
+
const requestedName = options.requested ?? Object.keys(providers)[0];
|
|
24
|
+
const requested = providers[requestedName];
|
|
25
|
+
if (!requested) {
|
|
26
|
+
const known = Object.keys(providers).join(", ");
|
|
27
|
+
throw new Error(`Unknown provider "${requestedName}". Available providers: ${known}`);
|
|
28
|
+
}
|
|
29
|
+
// Registration order, requested provider first.
|
|
30
|
+
const candidates = [
|
|
31
|
+
requested,
|
|
32
|
+
...Object.values(providers).filter((p) => p.name !== requested.name),
|
|
33
|
+
];
|
|
34
|
+
const warnings = [];
|
|
35
|
+
let firstError;
|
|
36
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
37
|
+
const candidate = candidates[i];
|
|
38
|
+
try {
|
|
39
|
+
const inbox = await candidate.createInbox({ label: options.label });
|
|
40
|
+
return {
|
|
41
|
+
inbox,
|
|
42
|
+
switched: i > 0,
|
|
43
|
+
warnings,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
firstError ??= err;
|
|
48
|
+
warnings.push(`${candidate.name}: ${describe(err)}`);
|
|
49
|
+
// A non-transient 4xx on the REQUESTED provider is a real request
|
|
50
|
+
// problem (bad payload, blocked domain…) — retrying others would just
|
|
51
|
+
// mask it. Only when the user did not explicitly pick a provider do we
|
|
52
|
+
// fall through anyway, because they never asked for this one by name.
|
|
53
|
+
if (!isTransient(err) && options.requested)
|
|
54
|
+
throw err;
|
|
55
|
+
if (!failover)
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Every candidate failed. Re-throw the requested provider's original error
|
|
60
|
+
// (it names the provider the user actually asked for) with a failover note.
|
|
61
|
+
const base = describe(firstError);
|
|
62
|
+
const others = candidates.length - 1;
|
|
63
|
+
throw new ProviderError(requestedName, others > 0
|
|
64
|
+
? `${base} — failover also tried ${others} other provider(s) without success`
|
|
65
|
+
: base);
|
|
66
|
+
}
|
package/dist/core/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./types.js";
|
|
|
8
8
|
export { extractCode, htmlToText } from "./otp.js";
|
|
9
9
|
export * from "./state.js";
|
|
10
10
|
export { waitForMessage, sleep } from "./wait.js";
|
|
11
|
+
export { createInboxWithFailover } from "./failover.js";
|
|
11
12
|
export const providers = {
|
|
12
13
|
[mailTm.name]: mailTm,
|
|
13
14
|
[mailGw.name]: mailGw,
|
package/dist/mcp.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { DEFAULT_PROVIDER, getProvider, resolveInbox, saveInbox, waitForMessage, htmlToText, } from "./core/index.js";
|
|
8
|
+
import { DEFAULT_PROVIDER, createInboxWithFailover, getProvider, resolveInbox, saveInbox, waitForMessage, htmlToText, } from "./core/index.js";
|
|
9
9
|
import { VERSION } from "./version.js";
|
|
10
10
|
function text(result, isError = false) {
|
|
11
11
|
return {
|
|
@@ -40,17 +40,24 @@ export async function startMcpServer() {
|
|
|
40
40
|
title: "Create a disposable inbox",
|
|
41
41
|
description: "Create a brand new disposable email inbox. The inbox is saved locally so the other tools can use it. Returns the full email address to use in sign-up forms.",
|
|
42
42
|
inputSchema: {
|
|
43
|
-
provider: z.string().optional().describe(`Provider name (default: "${DEFAULT_PROVIDER}", see the providers list)`),
|
|
43
|
+
provider: z.string().optional().describe(`Provider name (default: "${DEFAULT_PROVIDER}", see the providers list). If the provider is down, another one is used automatically unless no_failover is set`),
|
|
44
44
|
label: z.string().optional().describe("Optional label to identify this inbox"),
|
|
45
|
+
no_failover: z.boolean().optional().describe("Fail when the chosen provider is down instead of falling back to another one"),
|
|
45
46
|
},
|
|
46
|
-
}, async ({ provider, label }) => {
|
|
47
|
+
}, async ({ provider, label, no_failover }) => {
|
|
47
48
|
try {
|
|
48
|
-
|
|
49
|
-
const inbox = await
|
|
49
|
+
getProvider(provider); // unknown name = clean error before any network call
|
|
50
|
+
const { inbox, switched, warnings } = await createInboxWithFailover({
|
|
51
|
+
requested: provider,
|
|
52
|
+
label,
|
|
53
|
+
failover: !no_failover,
|
|
54
|
+
});
|
|
50
55
|
await saveInbox(inbox);
|
|
51
56
|
return text({
|
|
52
57
|
ok: true,
|
|
53
58
|
inbox: { address: inbox.address, provider: inbox.provider, label: inbox.label },
|
|
59
|
+
...(switched ? { failover: { requested: provider ?? DEFAULT_PROVIDER, used: inbox.provider } } : {}),
|
|
60
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
54
61
|
hint: `Use address "${inbox.address}" in the sign-up form, then call wait_for_code after submitting it.`,
|
|
55
62
|
});
|
|
56
63
|
}
|
package/dist/version.js
CHANGED
package/llms.txt
CHANGED
|
@@ -24,11 +24,13 @@ inboxes when done. It is designed agent-first: every CLI command supports
|
|
|
24
24
|
|
|
25
25
|
## CLI (binary: `tossinbox`)
|
|
26
26
|
|
|
27
|
-
- `tossinbox spawn` — create a new disposable inbox (flags: `-p provider`, `-l label`)
|
|
27
|
+
- `tossinbox spawn` — create a new disposable inbox (flags: `-p provider`, `-l label`);
|
|
28
|
+
if the provider is down, another one is used automatically (`--no-failover` opts out)
|
|
28
29
|
- `tossinbox list` — list messages in an inbox (flag: `-a address`)
|
|
29
30
|
- `tossinbox read <id>` — read a full message by id
|
|
30
31
|
- `tossinbox wait --code` — poll until a message arrives and print its verification code
|
|
31
32
|
(flags: `-f sender`, `-s subject`, `-t timeout`, `-i interval`)
|
|
33
|
+
- `tossinbox watch` — stream new messages as they arrive until Ctrl-C (NDJSON with `--json`)
|
|
32
34
|
- `tossinbox inboxes` — list locally saved inboxes
|
|
33
35
|
- `tossinbox toss` — delete an inbox server-side and wipe local state (`--all` for all)
|
|
34
36
|
- `tossinbox providers` — list providers (default: `mailtm`; also `mailgw`, `guerrillamail`, `tempmaillol`, `tempmailio`, `tempmailplus`, `maildrop`)
|
|
@@ -59,7 +61,9 @@ npx tossinbox@latest spawn
|
|
|
59
61
|
|
|
60
62
|
## Notes
|
|
61
63
|
|
|
62
|
-
- Providers: `mailtm` (default)
|
|
64
|
+
- Providers: `mailtm` (default), `mailgw`, `guerrillamail`, `tempmaillol`,
|
|
65
|
+
`tempmailio`, `tempmailplus`, `maildrop`; no API keys required. When a
|
|
66
|
+
provider is down, `spawn` fails over to another one automatically.
|
|
63
67
|
- Local state is stored in `~/.tossinbox/state.json` (override with the
|
|
64
68
|
`TOSSINBOX_STATE` environment variable) with 0600 permissions.
|
|
65
69
|
- TossInbox intentionally has no bulk mode; use it for privacy and testing and
|