[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81330":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":13,"stars7d":13,"stars30d":14,"stars90d":13,"forks30d":13,"starsTrendScore":13,"compositeScore":13,"rankGlobal":10,"rankLanguage":10,"license":15,"archived":16,"fork":16,"defaultBranch":17,"hasWiki":18,"hasPages":18,"topics":19,"createdAt":10,"pushedAt":10,"updatedAt":20,"readmeContent":21,"aiSummary":22,"trendingCount":13,"starSnapshotCount":13,"syncStatus":23,"lastSyncTime":24,"discoverSource":25},81330,"throwaway","sslboard\u002Fthrowaway","sslboard","An open source Cloudflare Worker app and API to check if email addresses are throwaway\u002Fdisposable or valid","https:\u002F\u002Fthrowaway.sslboard.com\u002F",null,"Python",44,0,1,"MIT License",false,"main",true,[],"2026-06-12 02:04:13","# throwaway\n\nA Cloudflare Worker that detects disposable\u002Ftemporary email domains, invalid TLDs, and non-existent domains (no MX records), exposed as a fast JSON API. Ships 72K+ domains in a ~173KB binary bloom filter. Uses [tldts](https:\u002F\u002Fgithub.com\u002Fnicolo-ribaudo\u002Ftldts) for TLD validation and Cloudflare DNS-over-HTTPS for MX resolution. Includes a clean web UI at `\u002F` for quick checks and `\u002Fllms.txt` for AI agent discovery.\n\n**Live deployment:** [throwaway.sslboard.com](https:\u002F\u002Fthrowaway.sslboard.com)\n\n[![Deploy to Cloudflare Workers](https:\u002F\u002Fdeploy.workers.cloudflare.com\u002Fbutton)](https:\u002F\u002Fdeploy.workers.cloudflare.com\u002F?url=https:\u002F\u002Fgithub.com\u002Fsslboard\u002Fthrowaway)\n\n## Honest context\n\n**This project was written almost entirely by AI.** I needed a disposable-email checker for [SSLBoard](https:\u002F\u002Fsslboard.com) (a free cybersecurity assessment tool) and I used Claude to build it. I'm sharing it as open source because the underlying approach (a binary bloom filter served from a Cloudflare Worker, no external calls) is genuinely useful and I haven't seen it done this way before.\n\nI understand the code, I can maintain it, and I'm happy to be accountable for it. But I'd rather be upfront than have someone dig through the commit history wondering why it looks the way it does.\n\n**Why I needed this:** About 22% of SSLBoard users sign up with a disposable email. That's not people protecting their privacy from a corporation. It's people scanning infrastructure they don't own, anonymously, with no way to follow up or hold anyone accountable. Blocking disposable addresses isn't anti-privacy; it's anti-abuse in a specific context where anonymity enables harm. Your use case may differ, and the tool is neutral: it just reports whether a domain is known-disposable.\n\n**Coverage gaps:** The domain list comes from [disposable\u002Fdisposable](https:\u002F\u002Fgithub.com\u002Fdisposable\u002Fdisposable), a community-maintained blocklist. It won't catch every disposable provider, especially newer ones. If you find a miss, [open an issue upstream](https:\u002F\u002Fgithub.com\u002Fdisposable\u002Fdisposable\u002Fissues) or submit a PR here with a regression test.\n\n## How It Works\n\nAt build time, `npm run build:filter` fetches the [disposable\u002Fdisposable](https:\u002F\u002Fgithub.com\u002Fdisposable\u002Fdisposable) list (72K+ entries) and compiles it into a **bloom filter** (a space-efficient probabilistic data structure). The filter is stored as a raw `.bin` file and loaded via Cloudflare Workers' [Data rule](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fwrangler\u002Fconfiguration\u002F#rules) as an `ArrayBuffer` at module load time. No base64, no encoding overhead, zero decode cost.\n\nAt request time, [tldts](https:\u002F\u002Fgithub.com\u002Fnicolo-ribaudo\u002Ftldts) parses the domain to determine whether the TLD is a real, ICANN-recognized public suffix. This catches addresses like `user@fake.notarealtld` that have no chance of receiving mail.\n\nAdditionally, each domain is checked for MX records via Cloudflare DNS-over-HTTPS (`cloudflare-dns.com`). Domains without MX records can't receive email, so even a domain with a valid TLD but no mail server is flagged (`has_mx: false`). The lookup has a 3-second timeout and fails open (returns `false` on error).\n\n### Bloom Filter Properties\n\n| Property            | Value                                    |\n| ------------------- | ---------------------------------------- |\n| Items               | ~72K domains                             |\n| Filter size         | ~173 KB                                  |\n| False positive rate | ~0.01% (1 in 10,000)                    |\n| False negatives     | **Zero**                                 |\n| Hash functions      | 10 (double-hashing from 2 cyrb53 hashes) |\n\n**On false positives:** At ~0.01%, a false positive means roughly 1 in 10,000 legitimate users gets incorrectly flagged. Bloom filters guarantee **zero false negatives**: a known-disposable domain will never slip through. But the false positive rate is real and depends on filter tuning. Whether that matters depends on your context. In B2C products (forums, messaging apps, consumer signups), anonymity is often a legitimate need, and blocking disposable domains works against your users. Throwaway is designed for B2B scenarios where verified identity is part of the value proposition. In SSLBoard's case, users perform internet-wide security scans that can look like attacks. A real email address creates accountability and gives us a way to follow up. A disposable address lets someone scan infrastructure they don't own with no trace. That's why the tradeoff is acceptable here, but it may not be for you.\n\n## API\n\n### `GET \u002F`\n\nMinimal web UI with a single input field to check emails. Shows three verdicts: **LEGITIMATE**, **DISPOSABLE**, or **INVALID**.\n\n### `GET \u002Fcheck?email=user@domain.com`\n\nCheck a single email address.\n\n```json\n{\n\t\"email\": \"user@mailinator.com\",\n\t\"domain\": \"mailinator.com\",\n\t\"valid_tld\": true,\n\t\"has_mx\": true,\n\t\"disposable\": true,\n\t\"should_reject\": true\n}\n```\n\n### `GET \u002Fcheck?domain=mailinator.com`\n\nCheck a single domain.\n\n```json\n{ \"domain\": \"mailinator.com\", \"valid_tld\": true, \"has_mx\": true, \"disposable\": true, \"should_reject\": true }\n```\n\n### `POST \u002Fcheck`\n\nBatch check emails or domains.\n\n**Emails:**\n\n```json\n{\n\t\"emails\": [\"user@mailinator.com\", \"john@gmail.com\", \"test@fake.notarealtld\"]\n}\n```\n\nResponse:\n\n```json\n{\n\t\"results\": [\n\t\t{\n\t\t\t\"email\": \"user@mailinator.com\",\n\t\t\t\"domain\": \"mailinator.com\",\n\t\t\t\"valid_tld\": true,\n\t\t\t\"has_mx\": true,\n\t\t\t\"disposable\": true,\n\t\t\t\"should_reject\": true\n\t\t},\n\t\t{\n\t\t\t\"email\": \"john@gmail.com\",\n\t\t\t\"domain\": \"gmail.com\",\n\t\t\t\"valid_tld\": true,\n\t\t\t\"has_mx\": true,\n\t\t\t\"disposable\": false,\n\t\t\t\"should_reject\": false\n\t\t},\n\t\t{\n\t\t\t\"email\": \"test@fake.notarealtld\",\n\t\t\t\"domain\": \"fake.notarealtld\",\n\t\t\t\"valid_tld\": false,\n\t\t\t\"has_mx\": false,\n\t\t\t\"disposable\": false,\n\t\t\t\"should_reject\": true\n\t\t}\n\t]\n}\n```\n\n**Domains:**\n\n```json\n{\n\t\"domains\": [\"mailinator.com\", \"gmail.com\"]\n}\n```\n\nResponse:\n\n```json\n{\n\t\"results\": [\n\t\t{ \"domain\": \"mailinator.com\", \"valid_tld\": true, \"has_mx\": true, \"disposable\": true, \"should_reject\": true },\n\t\t{ \"domain\": \"gmail.com\", \"valid_tld\": true, \"has_mx\": true, \"disposable\": false, \"should_reject\": false }\n\t]\n}\n```\n\n### `GET \u002Fstats`\n\nReturns filter metadata.\n\n```json\n{\n\t\"itemCount\": 121570,\n\t\"bitCount\": 2330512,\n\t\"hashCount\": 14,\n\t\"byteSize\": 291314,\n\t\"falsePositiveRate\": 0.0001\n}\n```\n\n### `GET \u002Fllms.txt`\n\nMachine-readable API documentation for AI agents. Plain text markdown.\n\n### Response Fields\n\n| Field        | Type    | Meaning                                                                                                 |\n| ------------ | ------- | ------------------------------------------------------------------------------------------------------- |\n| `valid_tld`  | boolean | `true` if the domain ends in a real ICANN-recognized TLD. `false` means the address can't receive mail. |\n| `has_mx`     | boolean | `true` if the domain has MX records (can receive email). `false` means no mail server exists.           |\n| `disposable` | boolean | `true` if the domain is in the disposable-email blocklist. Only meaningful when `valid_tld` is `true`.  |\n| `should_reject` | boolean | `true` when rules 1–3 below apply (invalid TLD, no MX, or disposable); `false` only for rule 4 (accept). |\n\n### Decision Logic\n\n`should_reject` is derived from these rules:\n\n1. `valid_tld: false` → **reject** (domain is not real)\n2. `has_mx: false` → **reject** (no mail server, can't receive email)\n3. `valid_tld: true` + `has_mx: true` + `disposable: true` → **reject** (known throwaway provider)\n4. `valid_tld: true` + `has_mx: true` + `disposable: false` → **accept** (looks legitimate)\n\n### Error Responses\n\nAll errors return `{\"error\": \"message\"}` with appropriate status codes:\n\n| Status | Meaning                            |\n| ------ | ---------------------------------- |\n| `400`  | Missing\u002Finvalid parameters or body |\n| `404`  | Unknown path                       |\n| `405`  | Unsupported HTTP method            |\n\n## Performance\n\n- **Bloom filter lookup**: pure arithmetic, microsecond responses\n- **MX resolution**: single DNS-over-HTTPS call to Cloudflare (3s timeout, fails open)\n- **Zero cold-start overhead**: filter loaded as a `Uint8Array` at module load time\n- **One runtime dependency**: `tldts` for TLD validation (bundled by Wrangler)\n\n## Deploy Your Own\n\n```bash\ngit clone \u003Cthis-repo>\ncd throwaway-worker\nnpm install\nnpm run build:filter    # Generate bloom filter from disposable domain list\nnpm run dev             # Local development\nnpm run deploy          # Deploy to Cloudflare\n```\n\n### Requirements\n\n- A [Cloudflare account](https:\u002F\u002Fdash.cloudflare.com\u002Fsign-up)\n- [Wrangler CLI](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fwrangler\u002F) (installed as a dev dependency)\n\n### Regenerate the Filter\n\nIf you want to update the domain list:\n\n```bash\nnpm run build:filter\n```\n\nThis re-fetches the domain list from [disposable\u002Fdisposable](https:\u002F\u002Fgithub.com\u002Fdisposable\u002Fdisposable) and writes fresh `src\u002Fgenerated\u002Ffilter.bin` and `src\u002Fgenerated\u002Ffilter-meta.ts` files.\n\n## End-to-end tests (Playwright)\n\nThe `e2e\u002F` specs drive the `\u002F` UI in a browser and assert the verdict text (e.g. legitimate, disposable, no MX records, invalid) against a **local** Worker.\n\n**Install browsers once** (after `npm install`):\n\n```bash\nnpx playwright install chromium\n```\n\n**Run the suite**:\n\n```bash\nnpm run test:e2e\n```\n\nPlaywright starts `wrangler dev` on **port 8788** and targets that URL. If `src\u002Fgenerated\u002Ffilter.bin` is missing, the dev-server command runs `npm run build:filter` first. When `CI` is set in the environment, an existing server on that port is not reused. The Worker resolves MX records over the network, so the runner needs outbound HTTPS (for example to `cloudflare-dns.com`).\n\n**Debug in the Playwright UI**:\n\n```bash\nnpx playwright test --ui\n```\n\n**CI**: install browsers in the job (for example `npx playwright install chromium` or `npx playwright install --with-deps chromium` on Linux), then run `npm run test:e2e`.\n\n## License\n\nMIT, by the people at [SSLBoard.com](https:\u002F\u002Fsslboard.com)\n","throwaway 是一个开源的 Cloudflare Worker 应用和 API，用于检测电子邮件地址是否为一次性\u002F临时邮箱或有效邮箱。项目使用 TypeScript 编写，通过二进制布隆过滤器存储超过 72,000 个域名（大小约为 173KB），利用 tldts 进行顶级域名验证，并通过 Cloudflare 的 DNS-over-HTTPS 检查 MX 记录。它提供了一个简洁的网页界面方便快速检查，同时支持 AI 代理发现。此工具适用于需要防止滥用匿名邮箱的场景，如网络安全评估服务中阻止用户使用一次性邮箱注册，从而减少恶意行为。",2,"2026-06-11 04:04:39","CREATED_QUERY"]