docs: fix broken star history chart in READMEs and docs
The star history chart in the READMEs and vitepress docs is currently broken because the upstream service no longer works due to GitHub stargazer API restrictions. Switch the chart images to the star-history.dera.page mirror, which uses a different data source that requires no API token, so the chart renders correctly again.
Co-authored-by: SimonFoobar648 <245426116+SimonFoobar648@users.noreply.github.com>
* feat: add setting to disable auto-loading external images in emails
Adds a privacy setting (default off) that blocks remote images in email
content until the user explicitly loads them per message. Blocked images
are replaced with a placeholder; a banner allows one-click loading.
Closes#1073
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): block remote content with DOMPurify and an allowlist policy
Address review on the blocking logic. The first pass matched quoted
`<img src="http...">` with a regex, which left unquoted src, srcset,
`<source>`, CSS background-image, SVG `<image href>` and entity-encoded
schemes fetching as usual, and replaced only `src` on an element that also
carried `srcset` -- so the browser still had a remote candidate to prefer
while the UI claimed the image was blocked.
Two changes rather than a wider regex:
Sanitising is delegated to DOMPurify, which is already a dependency. The
hard part here is not enumerating attributes but surviving the parser: a
hand-written pass over a DOMParser tree still missed that `<noscript>` is
parsed as markup where scripting is off and as raw text where it is on, so a
`</noscript>` smuggled into an attribute value reopens the document at
render time and revives an `<img>` the cleaner never saw. Elements that
fetch by themselves or change how relative URLs resolve -- base, meta,
script, link, iframe, object, embed, noscript -- are dropped in this mode.
`<style>` is kept so layout survives, with its url(), image-set() and
@import references filtered.
URL classification is an allowlist. Asking "does this look remote?" means
enumerating every disguise -- backslash authorities, tab/newline/control
characters the URL parser strips, CSS escapes, schemes with no slashes --
and losing to the first one not thought of. Asking "can I prove this is
local?" fails closed instead: cid:, data:image/, blob: and relative paths
are kept, everything else is blocked. Relative paths are only safe because
`<base>` is removed, which is what stopped it re-pointing them at a tracker.
The blocked URL is discarded rather than parked in a data-* attribute, so
"the cleaned body contains no remote URL at all" is directly assertable;
restoring images re-renders from the untouched source.
Also: blob: is added to the allowed schemes -- DOMPurify's default list
omits it, and email-parser rewrites cid: attachments into blob: URLs, so
without it every inline image would be stripped along with the trackers.
The policy lives in its own module with its own tests (30 attack vectors,
7 preservation cases); email-parser.js goes back to MIME parsing only. The
per-mail override no longer initialises from the global setting, and the
banner reports the blocked count as the PR description promised.
Refs #1073
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Refresh dependencies and lockfiles across frontend, Worker, Pages, and VitePress documentation packages. Update Wrangler to 4.114.0 and align Cloudflare Workers types with its peer requirements.
Validated with frontend tests/build, Worker build/lint, docs build, and repository E2E.
Sanitize HTML announcements in both the About page and startup notification through a shared DOMPurify helper. Add regression tests and bilingual changelog entries.
Co-authored-by: tuanaiseo <tuanaiseo@gmail.com>
Fix IMAP flag persistence so read/unread state survives reconnects, and align SEARCH/FETCH behavior with persisted flags.
Co-authored-by: bounce12340 <bounce12340@users.noreply.github.com>
Cover the junk-mail policy behavior fixed in #1085:
- none/neutral results for SPF/DKIM/DMARC are treated as the method
being absent and do not trigger JUNK_MAIL_CHECK_LIST rejection
- explicit fail results are still rejected
- JUNK_MAIL_FORCE_PASS_LIST only accepts an explicit pass
Run with: node --test-isolation=none --test worker/src/email/junk_mail_policy.test.mjs
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(mailbox): add list view mode
- Add a toggleable list view for the mailbox, with a settings option and back button.
- deselect mail on second click in list view
- set current mail on row click in multi-action mode
* feat(mailbox): add configurable body preview line clamp
Allow users to set the number of preview lines (0–5) for mail body in the list view via a slider in Appearance settings. Includes i18n support for the new option and its "Off" state.
* chore: clarify some i18n message in settings
include the following changes:
- The original "Mailbox Split Size" to "Left list width in two-column mailbox view"
- The description of new feature "Full-width mailbox list view"
sync all languages with the updated message
* docs: update changelog with recent UI improvements
- Added mailbox full-width list view and body preview lines settings
- Extended left panel width ratio range to 0
- Included English changelog translations
* docs: fix CHANGELOG improvements types
* fix: enable mail list preview line clamp settings on mobile
* fix: validate AI extracted link domains
* fix: validate extracted links against full email content
* refactor: simplify AI link domain guard
* refactor: keep AI domain fix prompt-only
docs: add Resend DNS-only proxy warning to send-mail config
Resend domain verification CNAME records must use DNS-only (gray
cloud) on Cloudflare. Proxied (orange cloud) records prevent
verification, and a single failed attempt can take hours before
retry. This is a recurring issue (#515) that the Resend setup
docs did not warn about.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
* fix: add page header padding for mobile layout
* fix: limit page header padding to mobile layout
* docs: update changelog for mobile header fix
---------
Co-authored-by: dreamhunter2333 <dreamhunter2333@gmail.com>
feat: add regex fallback for verification code extraction without Workers AI
When AI email extraction is enabled but no Workers AI binding is available,
fall back to a built-in, zero-dependency regex extractor so self-hosted
deployments without Workers AI still surface verification codes in Telegram
notifications and webhooks.
- Add worker/src/email/extract_code.ts: rule-based multilingual
(English / Chinese / Japanese / Korean) verification-code extractor with
year and YYYYMMDD date rejection to avoid false positives.
- ai_extract.ts: share the allowlist check and content parsing across both
paths, extract a saveExtractMetadata helper, and use the regex fallback
when env.AI is absent.
- Reuse the existing aiExtractResult pipeline (auth_code type), so Telegram
and webhook output need no changes.
- Update bilingual CHANGELOG and AI-extract feature docs.
Random subdomain mailbox creation only generates addresses; mail delivery
depends on DNS / Cloudflare Email Routing covering *.<base-domain>.
Cloudflare Email Routing does not inherit apex configuration onto
subdomains, so a wildcard `*` MX record on the base domain is required
for random subdomains to actually receive mail.
- Add `[!IMPORTANT]` block in subdomain.md (zh/en) explaining the two
deliverable paths: DNS-only wildcard MX (recommended for random
subdomains) vs Cloudflare dashboard "Add subdomain"
- Link to Cloudflare Email Routing — Subdomains official docs from
worker-vars.md and subdomain.md
- Instruct copying apex MX records to host `*` preserving each record's
priority/target, instead of hardcoding specific MX targets
- Shorten frontend `randomSubdomainTip` for CreateAccount and Login
views (6 locales: zh/en/de/es/ja/pt-BR), drop Markdown backticks
(Vue text interpolation renders them literally), and point users to
the docs instead of embedding DNS instructions
- Trim overlap between existing `[!NOTE]` and new `[!IMPORTANT]` in
subdomain.md
- Update CHANGELOG.md / CHANGELOG_EN.md under v1.9.0(main)
Closes#1035Closes#1026
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: update AI extract default model
* fix: update e2e worker node version
* fix: use node lts for e2e worker
* fix: align AI model and CI node version
* ci: allow docs deploy without GitHub release
* fix: use workflow run branch for docs tag fallback
---------
Co-authored-by: dreamhunter2333 <dreamhunter2333@gmail.com>