Cloudsteading
Example delivery

Cindermail

Disposable domain addresses delivered to Discord, Telegram or Slack.

Cindermail is a self-hosted SimpleLogin alternative built on Cloudflare (D1, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.

Source & license

Upstream license: MIT

License TL;DR

You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.

Explain MIT in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@psalm2517

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Free tier eligible within limits

The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply.

Hosting requirements
  • Use owned-domain Email Routing mode, not mail.tm polling, for the Cloudflare-only receiving assessment; domain registration is separate.
  • Discord/Telegram/Slack credentials and platform permissions are required; their attachment limits and account policies still apply.
  • Measure HTML parsing CPU on real messages against Free's 10ms limit; large/adversarial messages may require Workers Paid.
  • Address counts, incoming mail and daily cleanup must remain within Workers/D1 quotas.
  • Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured.
  • D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: ac0ac90. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • simplelogin ↗

    rmail/stargazers) </div> --- Give out `x7k2p9qzrm@yourdomain.com` instead of your real address. Mail sent to it gets parsed and delivered straight to you, on Discord, Telegram, or Slack. Torch it when you're done. ![Example delivery](docs/images/example-dm.png) Addresses sit on a domain you own, so nothing flags them as disposable the way public temp-mail domains get flagged. Runs on Cloudflare's free tier: Email Routing receives, D1 stores, one Worker does the rest. Nothing to keep alive. No domain? Leave one setting blank and it uses mail.tm's instead. #

  • workers ↗

    is specific to one deployment, so this file can be cloned // or deployed by anyone as-is. Your domain and limits go in secrets // (`wrangler secret put`, or the deploy button's own prompts, which cover // everything listed in .dev.vars.example). Secrets stay out of the repo // and survive deploys, which plaintext dashboard variables do not. // // DISPOSABLE_DOMAIN is what picks the Worker's mode: set it to a domain // you own to receive through Email Routing, or leave it unset to run on // mail.tm instead, needing no domain, no DNS and no Email Ro

  • d1 ↗

    "vars": { "ADAPTERS": "discord,telegram,slack" }, "d1_databases": [ { "binding": "DB", "database_name": "cinderbox", "database_id": "80611409-6ad6-4226-a988-238e3d2e01fa" } ], // Daily cleanup, plus the mail.tm poll. The poll returns immediately // without touching D1 when DISPOSABLE_DOMAIN is set, so it costs nothing // on a domain-based deployment. "triggers": { "crons": ["0 3 * * *", "*/1 * * * *"] }, // Was dashboard-only originally and got turned off by a plain deploy, same // failure mode as the D1 bindin

  • free-tier-eligible ↗

    the dashboard under Domains & Routes and it // persists. Pinning it here instead would put a hostname only one account // owns into everyone's config. "name": "cinderbox", "main": "src/worker.ts", "compatibility_date": "2024-09-23", "compatibility_flags": ["nodejs_compat"], // Both default to false once any route is configured, and a workers.dev // URL is worth keeping: it's the fallback when no custom domain is // attached, and it's a usable Discord interactions endpoint on its own. "workers_dev": true, "preview_urls": true, "vars": {

  • free-tier-eligible ↗

    ount Manager. | | Requests<sup>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 second

  • free-tier-eligible ↗

    rs Paid](https://developers.cloudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/obs

  • MIT ↗

    MIT License Copyright (c) 2026 amandoti.win Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this

  • architecture ↗

    is specific to one deployment, so this file can be cloned // or deployed by anyone as-is. Your domain and limits go in secrets // (`wrangler secret put`, or the deploy button's own prompts, which cover // everything listed in .dev.vars.example). Secrets stay out of the repo // and survive deploys, which plaintext dashboard variables do not. // // DISPOSABLE_DOMAIN is what picks the Worker's mode: set it to a domain // you own to receive through Email Routing, or leave it unset to run on // mail.tm instead, needing no domain, no DNS and no Email Ro

Upstream screenshot · psalm2517/cindermail repository contributors ↗. Depicts the upstream project. We have not deployed and tested a fresh installation here.

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

SimpleLogin logoSimpleLogin ↗

Editorial workflow alternative: Receiving through disposable addresses on an owned domain and delivering messages into chat; outbound replies, mailbox UI and full alias-provider parity not asserted.

See supporting source ↗
external SaaS target
varies
→ D1 + Workers

How it works

The shape of Cindermail on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points2
cinderbox
wrangler.jsonc
cinderbox-telegram
wrangler.telegram.jsonc
↓
App
cinderbox
entry
Cloudflare Workers
Entrypoint: src/worker.tsConfigured cron (UTC): 0 3 * * *Configured cron (UTC): */1 * * * *
cinderbox-telegram
entry
Cloudflare Workers
Entrypoint: src/telegram-worker.ts
↓

Configuration and workflow sources

Reviewed commit ac0ac909c2ea. Files were read as data; upstream applications and CI jobs were not executed.

Partial source coverage: 21 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.

Deployment configuration · 2 files
wrangler.jsonc ↗

Cloudflare Workers · compatibility 2024-09-23

cinderbox · default

Entrypoint: src/worker.ts

Cron triggers (UTC): 0 3 * * * · */1 * * * *

  • DB → D1
wrangler.telegram.jsonc ↗

Cloudflare Workers · compatibility 2024-09-23

cinderbox-telegram · default

Entrypoint: src/telegram-worker.ts

  • DB → D1

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

src/worker.ts ↗
  • L83 · fetch handler exported · references DB, DISCORD_PUBLIC_KEY, DISPOSABLE_DOMAIN · calls renderCounterPage, handleTelegramWebhookRequest, createD1Executor, handleSlackCommandRequest, getCounters, Response.json, request.headers.get, request.text, verifyKey, JSON.parse, buildCommandConfig, usesOwnDomain, createAddress, handleInteraction
  • L162 · email handler exported · references DB · calls createD1Executor, createDispatcher, buildAdapters, handleInboundEmail
  • L168 · scheduled handler exported · references DB · calls createD1Executor, pollOnce, createDispatcher, buildAdapters, sendExpiryWarnings, runMailtmCleanup
  • L63 · usesOwnDomain calls (conditional paths may differ): env.DISPOSABLE_DOMAIN.trim
  • L67 · buildAdapters calls (conditional paths may differ): map, env.ADAPTERS.split, s.trim, enabled.includes, adapters.push, createDiscordAdapter, createTelegramAdapter, createSlackAdapter

Environment references: env.DISPOSABLE_DOMAIN · env.ADAPTERS · env.DISCORD_TOKEN · env.TELEGRAM_BOT_TOKEN · env.SLACK_BOT_TOKEN · env.DB · env.DISCORD_PUBLIC_KEY

src/telegram-worker.ts ↗
  • L21 · fetch handler exported · references DB · calls handleTelegramWebhookRequest, createD1Executor

Environment references: env.DB

src/adapters/discord/index.ts ↗
  • L28 · truncateAtLineBoundary calls (conditional paths may differ): text.split, text.slice
  • L41 · createDiscordAdapter calls (conditional paths may differ): createDM, escapeMarkdown, mail.html.slice, htmlToText, truncateAtLineBoundary, notes.push, files.push, encode, readableText.trim, files.reduce, notes.join, sendMessage, String
src/adapters/telegram/index.ts ↗
  • L16 · htmlToText calls (conditional paths may differ): coreHtmlToText
  • L27 · truncateAtLineBoundary calls (conditional paths may differ): text.split, text.slice
  • L40 · createTelegramAdapter calls (conditional paths may differ): mail.html.slice, htmlToText, truncateAtLineBoundary, notes.push, files.push, encode, readableText.trim, toSend.push, notes.join, sendMessage, sendDocument, String
src/adapters/telegram/webhook.ts ↗
  • L23 · usesOwnDomain calls (conditional paths may differ): env.DISPOSABLE_DOMAIN.trim
  • L31 · handleTelegramWebhookRequest calls (conditional paths may differ): request.headers.get, request.json, buildCommandConfig, usesOwnDomain, createAddress, handleUpdate, sendMessage

Environment references: env.DISPOSABLE_DOMAIN · env.TELEGRAM_BOT_TOKEN · env.TELEGRAM_WEBHOOK_SECRET

src/adapters/slack/index.ts ↗
  • L16 · htmlToText calls (conditional paths may differ): coreHtmlToText
  • L27 · truncateAtLineBoundary calls (conditional paths may differ): text.split, text.slice
  • L40 · createSlackAdapter calls (conditional paths may differ): mail.html.slice, htmlToText, truncateAtLineBoundary, notes.push, files.push, encode, readableText.trim, toSend.push, notes.join, postMessage, uploadFile, String
src/adapters/slack/webhook.ts ↗
  • L18 · usesOwnDomain calls (conditional paths may differ): env.DISPOSABLE_DOMAIN.trim
  • L26 · handleSlackCommandRequest calls (conditional paths may differ): request.headers.get, request.text, verifySlackSignature, form.get, buildCommandConfig, usesOwnDomain, createAddress, handleSlashCommand, Response.json

Environment references: env.DISPOSABLE_DOMAIN · env.SLACK_SIGNING_SECRET

src/core/config.ts ↗
  • L16 · parseIntEnv calls (conditional paths may differ): Number.parseInt, Number.isFinite
  • L24 · rateLimitFromEnv calls (conditional paths may differ): parseIntEnv
  • L40 · buildCommandConfig calls (conditional paths may differ): parseIntEnv, rateLimitFromEnv

Environment references: env.MAX_ACTIVE_ADDRESSES · env.ADDRESS_TTL_SECONDS

src/adapters/discord/interactions.ts ↗
  • L73 · getAddressOption calls (conditional paths may differ): getOption
  • L81 · getIntegerOption calls (conditional paths may differ): Number.isInteger
  • L93 · handleInteraction calls (conditional paths may differ): getInvokingUserId, ephemeralReply, checkAndIncrement, handleNew, getIntegerOption, getOption, handleList, handleNote, getAddressOption, handleExtend, handleTorch, handleRemind, getBooleanOption
  • L152 · handleNew calls (conditional paths may differ): parseExpiry, ephemeralReply, countActiveAddresses, createAddressFn, describeExpiry
  • L183 · handleNote calls (conditional paths may differ): ephemeralReply, resolveAddressIdentifier, slice, note.trim, setAddressNote
  • L201 · handleList calls (conditional paths may differ): listActiveAddresses, ephemeralReply, addresses.map, a.note.replaceAll, join
  • L218 · handleExtend calls (conditional paths may differ): ephemeralReply, resolveAddressIdentifier, parseExpiry, extendAddress, Math.round
  • L256 · handleTorch calls (conditional paths may differ): ephemeralReply, resolveAddressIdentifier, revokeAddress
  • L273 · handleRemind calls (conditional paths may differ): getExpiryReminderPreference, ephemeralReply, setExpiryReminderPreference
src/core/db.ts ↗
  • L18 · randomAlphanumeric calls (conditional paths may differ): crypto.getRandomValues
  • L46 · randomShortId calls (conditional paths may differ): String, Math.floor, Math.random
  • L50 · generateUniqueShortId calls (conditional paths may differ): randomShortId, db.first
  • L64 · createAddress calls (conditional paths may differ): Math.floor, Date.now, randomAlphanumeric, generateUniqueShortId, db.run, incrementCreatedCounter
  • L105 · registerAddress calls (conditional paths may differ): Math.floor, Date.now, generateUniqueShortId, db.run, incrementCreatedCounter
  • L135 · setAddressNote calls (conditional paths may differ): db.run, note.trim
  • L152 · getAddress calls (conditional paths may differ): db.first
  • L159 · getAddressByShortId calls (conditional paths may differ): db.first
  • L172 · listActiveAddresses calls (conditional paths may differ): Math.floor, Date.now, db.all
  • L188 · listActiveAddressesWithReceiverData calls (conditional paths may differ): Math.floor, Date.now, db.all
  • L200 · listExpiredAndRevoked calls (conditional paths may differ): Math.floor, Date.now, db.all
  • L213 · countActiveAddresses calls (conditional paths may differ): Math.floor, Date.now, db.first
  • L232 · extendAddress calls (conditional paths may differ): Math.floor, Date.now, db.run
  • L252 · revokeAddress calls (conditional paths may differ): Math.floor, Date.now, db.run, incrementTorchedCounter
  • L272 · listAddressesNeedingExpiryWarning calls (conditional paths may differ): Math.floor, Date.now, db.all
  • L294 · markExpiryWarned calls (conditional paths may differ): Math.floor, Date.now, join, addresses.map, db.run
  • L309 · getExpiryReminderPreference calls (conditional paths may differ): db.first
  • L318 · setExpiryReminderPreference calls (conditional paths may differ): db.run
  • L339 · deleteExpiredAndRevoked calls (conditional paths may differ): Math.floor, Date.now, db.run
  • L357 · deleteStaleRateLimits calls (conditional paths may differ): Math.floor, Date.now, db.run
  • L374 · bumpCounter calls (conditional paths may differ): db.run, console.warn, String
  • L382 · incrementCreatedCounter calls (conditional paths may differ): bumpCounter
  • L386 · incrementTorchedCounter calls (conditional paths may differ): bumpCounter
  • L390 · incrementReceivedCounter calls (conditional paths may differ): bumpCounter
  • L407 · countOwners calls (conditional paths may differ): Math.floor, Date.now, db.first
  • L422 · getCounters calls (conditional paths may differ): db.first, countOwners, console.warn, String
src/core/dispatch.ts ↗
  • L8 · createDispatcher calls (conditional paths may differ): registry.set, registry.get, adapter.deliver, adapter.notify
src/core/email.ts ↗
  • L16 · handleInboundEmail calls (conditional paths may differ): message.to.toLowerCase, getAddress, Math.floor, Date.now, incrementReceivedCounter, PostalMime.parse, parsed.attachments.map, dispatcher.deliverMail, console.warn
src/core/expiry-warning.ts ↗
  • L20 · groupByOwner calls (conditional paths may differ): groups.get, existing.rows.push, groups.set
  • L36 · buildMessage calls (conditional paths may differ): rows.map, row.note.replaceAll, lines.join
  • L58 · sendExpiryWarnings calls (conditional paths may differ): listAddressesNeedingExpiryWarning, console.warn, String, values, groupByOwner, dispatcher.notifyOwner, buildMessage, markExpiryWarned, rows.map
src/receivers/mailtm/address.ts ↗
  • L21 · createMailtmAddress calls (conditional paths may differ): getActiveDomain, randomAlphanumeric, createAccount, registerAddress, JSON.stringify
src/receivers/mailtm/cleanup.ts ↗
  • L13 · runMailtmCleanup calls (conditional paths may differ): listExpiredAndRevoked, JSON.parse, getToken, deleteAccount, console.error, deleteExpiredAndRevoked, deleteStaleRateLimits
src/receivers/mailtm/poller.ts ↗
  • L8 · parseReceiverData calls (conditional paths may differ): JSON.parse
  • L27 · pollOnce calls (conditional paths may differ): listActiveAddressesWithReceiverData, parseReceiverData, getToken, listMessages, fetchSource, handleInboundEmail, deleteMessage, console.error
src/storage/d1.ts ↗
  • L3 · createD1Executor calls (conditional paths may differ): run, bind, db.prepare, first, all
src/adapters/discord/discord-rest.ts ↗
  • L23 · discordFetch calls (conditional paths may differ): fetch, catch, response.text
  • L40 · createDM calls (conditional paths may differ): discordFetch, JSON.stringify, response.json
  • L49 · sendMessage calls (conditional paths may differ): discordFetch, JSON.stringify, form.append, payload.files.map, payload.files.forEach
src/adapters/discord/html-to-text.ts ↗
  • L16 · escapeMarkdown calls (conditional paths may differ): text.replace
  • L20 · htmlToText calls (conditional paths may differ): coreHtmlToText
src/core/html-to-text.ts ↗
  • L31 · decodeEntities calls (conditional paths may differ): text.replace, parseInt, entity.slice, Number.isFinite, String.fromCodePoint, entity.toLowerCase
  • L86 · replaceLinks calls (conditional paths may differ): text.replace, trim, replace, inner.replace, decodeEntities, href.trim, url.startsWith, startsWith, url.toLowerCase, seenUrls.has, seenUrls.add, urls.push
  • L130 · htmlToText calls (conditional paths may differ): replace, html.replace, replaceLinks, text.replace, decodeEntities, trim, join, filter, map, text.split, line.replace, options.escape, options.formatLink, Number
src/adapters/telegram/telegram-rest.ts ↗
  • L18 · telegramFetch calls (conditional paths may differ): fetch, catch, response.text
  • L32 · sendMessage calls (conditional paths may differ): telegramFetch, JSON.stringify
  • L44 · sendDocument calls (conditional paths may differ): form.append, telegramFetch
Build and deployment pipeline · 1 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

CI · .github/workflows/ci.yml ↗

Triggers: push, pull_request

check · no job dependencies declared

  1. actions/checkout@v4actions/checkout@v4
  2. actions/setup-node@v4actions/setup-node@v4
  3. Shell commandnpm ci
  4. Shell commandnpm run typecheck
  5. Shell commandnpm test

Full upstream document by @psalm2517 · README.md · snapshot ac0ac90


Give out x7k2p9qzrm@yourdomain.com instead of your real address. Mail sent to it gets parsed and delivered straight to you, on Discord, Telegram, or Slack. Torch it when you're done.

Example delivery

Addresses sit on a domain you own, so nothing flags them as disposable the way public temp-mail domains get flagged. Runs on Cloudflare's free tier: Email Routing receives, D1 stores, one Worker does the rest. Nothing to keep alive.

No domain? Leave one setting blank and it uses mail.tm's instead.

Deploy

Deploy to Cloudflare

Forks the repo, creates the database, deploys the Worker, prompts for your domain and credentials for whichever platforms you fill in (Discord, Telegram, Slack, any combination, blank fields are skipped). Blank domain means mail.tm mode.

It can't load the database schema, and each platform has one manual step of its own after that: registering commands for Discord, pointing a webhook at the Worker for Telegram, creating the app itself for Slack. The database step is in docs/deploy-cloudflare.md; the rest is whichever of docs/discord-adapter.md, docs/telegram-adapter.md, or docs/slack-adapter.md matches what you filled in above.

Prefer a local clone
git clone https://github.com/psalm2517/cindermail.git
cd Cindermail
npm install && npm run setup

The wizard asks the same questions and writes the same config, plus it offers to set Discord's, Telegram's, and Slack's credentials interactively, one at a time. Use this if you'll be changing the code.

Commands

/new [expiry] [note] A fresh address. Permanent unless given an expiry in days.
/list Your addresses, with notes, expiry, and how many of your quota you're using.
/extend <address/id> [expiry] Change when one expires. expiry: 0 makes it permanent.
/note <address/id> [note] Label one. Blank clears it.
/remind [on/off] Opt in to a message a day before an address expires.
/torch <address/id> Kill it.

/list shows a short 5-digit id next to each address (#48213). /extend, /note, and /torch all accept that id instead: just the digits, no #.

Same six commands across Discord, Telegram, and Slack; exact syntax differs slightly per platform (Discord takes structured options, Telegram and Slack read plain text after the command, and Slack's are prefixed /cm- since Slack rejects bare generic command names). Discord and Slack replies are ephemeral, visible only to whoever ran the command; Telegram only works in a private chat with the bot for the same reason, since it has no ephemeral-reply equivalent. Details in docs/discord-adapter.md, docs/telegram-adapter.md, and docs/slack-adapter.md.

How it works

  1. /new mints a random address owned by whoever ran it.
  2. Give it out. Mail sent there comes back to you, not to wherever you used it.
  3. Mail arrives, the Worker looks up the owner. Unknown, expired or torched addresses are dropped: no bounce, nothing logged.
  4. Otherwise it's parsed (HTML to text, links intact, attachments forwarded) and delivered to you.

A daily cron deletes expired and torched addresses, clears stale rate-limit rows, and sends expiry reminders.

The Worker root serves a status page with running totals, also available as JSON at /counters. Counts only, no addresses or owners.

Limits

  • 5 active addresses per owner, configurable.
  • Message bodies cap at 1500 characters inline on Discord, 3500 on Telegram and Slack; longer is attached as message.txt.
  • Inbound HTML caps at 256KB before parsing. Parsing cost scales quadratically, and anyone who learns an address can send to it.
  • Attachments forward up to 25MB combined per email on Discord, or 50MB per file on Telegram and Slack. Anything over budget is dropped with a note, not the whole batch.

Code layout and tests: docs/architecture.md. Every setting: docs/configuration.md.

Planned

  • Private hosting as a service

  • Webview for reading full HTML emails on the web

  • Freemium public instance

  • Freemium API

AI disclosure

This project was built with AI assistance, directed by me.

License

MIT. See LICENSE.

Built with Cloudflare

Frequently asked about Cindermail

What is Cindermail?+

Cindermail is a self-hosted SimpleLogin alternative built on the Cloudflare developer platform. Disposable domain addresses delivered to Discord, Telegram or Slack.

What does Cindermail replace?+

Cindermail is listed as an alternative to SimpleLogin. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does Cindermail use?+

Cindermail is built on D1, Workers.

How much does Cindermail cost to run?+

The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply. Use owned-domain Email Routing mode, not mail.tm polling, for the Cloudflare-only receiving assessment; domain registration is separate. Discord/Telegram/Slack credentials and platform permissions are required; their attachment limits and account policies still apply. Measure HTML parsing CPU on real messages against Free's 10ms limit; large/adversarial messages may require Workers Paid. Address counts, incoming mail and daily cleanup must remain within Workers/D1 quotas. Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured. D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota. Check current Cloudflare pricing before deploying.

Is Cindermail open source?+

The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/psalm2517/cindermail/ac0ac909c2ea356dbbcef0e230a4e573a90a00a7/LICENSE. Source code and contributor credit are available at https://github.com/psalm2517/cindermail.

Discussion · 0

sign in to comment →
No comments yet — be the first.