Commit Graph
28 Commits
Author SHA1 Message Date
Julien HerrandClaude Sonnet 4.6 3aea41f862 feat: add ESLint, lint-staged, and update pre-commit hook + CI
- Add ESLint 9 flat config (eslint.config.mjs) with typescript-eslint
  recommended rules and eslint-config-prettier
- Add lint-staged to run eslint+prettier only on staged files
- Update pre-commit hook to use lint-staged instead of full prettier check
- Add `lint` and `format:check` scripts to package.json
- Add Lint step to CI workflow
- Fix resulting lint errors: unused vars (_ctx, _options, catch binding),
  any→unknown in type declarations, stale eslint-disable comments

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 09:49:20 +02:00
Julien HerrandClaude Sonnet 4.6 e93bbb8d3e feat: store email attachments in R2 and expose as RSS enclosures
Attachments from incoming emails are uploaded to an optional Cloudflare R2
bucket and exposed as <enclosure> elements in RSS and <link rel="enclosure">
in Atom feeds, served at /files/{id}/{filename} with immutable caching.

R2 is opt-in: if ATTACHMENT_BUCKET is not bound the feature is a no-op.
Attachments are cleaned up from R2 on email/feed deletion and during
size-based feed trimming. Adds MockR2 to the test setup.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 09:09:37 +02:00
Julien Herr 3e28246c61 docs: document FEED_MAX_SIZE_BYTES and proxy auth setup in README 2026-05-21 08:42:48 +02:00
Julien HerrandClaude Sonnet 4.6 caaa6a7ba6 feat: add external proxy auth support (Authelia/Authentik)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 08:39:10 +02:00
Julien HerrandClaude Sonnet 4.6 9eba4c34c6 feat: replace fixed 50-entry cap with size-based feed trimming
Emails are now trimmed from the oldest end when total serialised size
exceeds FEED_MAX_SIZE_BYTES (default 512 KB). Each EmailMetadata entry
stores its size so future trims are computed without re-reading KV.
Adds FEED_MAX_SIZE_BYTES, PROXY_TRUSTED_IPS and PROXY_AUTH_SECRET to Env.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 08:28:07 +02:00
Julien HerrandClaude Sonnet 4.6 984362f637 docs: add CLAUDE.md with project guidance for Claude Code
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 07:40:00 +02:00
Julien Herr 19b590a568 docs: mark Atom feed format as done in TODO 2026-05-21 07:36:14 +02:00
Julien Herr 1acc2f952c feat: mount /atom route in main app 2026-05-21 07:36:05 +02:00
Julien Herr b107803177 feat: add Atom 1.0 feed route at /atom/:feedId 2026-05-21 07:35:32 +02:00
Julien Herr 4d8d1fdc82 refactor: extract buildFeed helper, add generateAtomFeed 2026-05-21 07:34:29 +02:00
Julien HerrandClaude Sonnet 4.6 ad5bd9a79a docs: document Cloudflare Email Workers as primary ingestion method
Add setup instructions for Cloudflare Email Routing (Option A, recommended)
and reposition ForwardEmail as an optional alternative (Option B). Clarify
Cloudflare free plan includes Workers, KV, and Email Routing. Fix security
note scope and remove redundant/misleading content.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 00:36:44 +02:00
Julien HerrandClaude Sonnet 4.6 1c8b0ca194 ci: add CI workflow for format, typecheck, and test
Runs on push/PR to main. Also adds skipLibCheck to tsconfig to silence
pre-existing errors in vite/vitest declaration files that don't affect
the Worker build.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 00:07:25 +02:00
Julien HerrandClaude Sonnet 4.6 2e9cdd4364 test: add unit tests for EmailParser
Covers extractFeedId, decodeEncodedWords, and parseForwardEmailPayload
including edge cases: missing fields, structured vs text from, headerLines
vs raw headers string, RFC 2047 subject decoding.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 00:06:13 +02:00
Julien HerrandClaude Sonnet 4.6 41efee44ca refactor: replace custom escapeHtml with Hono's html template
Hono's `html` tagged template auto-escapes all interpolated values;
`raw()` is used for the email body which must render as HTML.
This removes the ad-hoc utility and aligns entries.ts with the
same pattern already used in admin.ts.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-21 00:05:35 +02:00
Julien HerrandClaude Sonnet 4.6 5308544672 refactor: simplify quick-win code after review
- Make feedId required in generateRssFeed (removes dead /emails/ fallback)
- Hoist loop-invariant conditional and remove intermediate variable
- Extract normalizeAllowedSenders() so JSON and form paths share same logic
- Move escapeHtml to src/utils/html.ts for reuse by admin.ts
- Parallelize the two independent KV puts in feed creation

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:55:17 +02:00
Julien HerrandClaude Sonnet 4.6 e140b433d8 docs: mark quick wins as done in TODO.md
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:52:26 +02:00
Julien HerrandClaude Sonnet 4.6 fdedbe13c4 feat: accept JSON on POST /admin/feeds/create and return {feedId, email, feedUrl}
When Content-Type is application/json, parse the request body as JSON and
return a JSON response instead of redirecting. Useful for automation tools
(e.g. Terraform/OpenTofu provisioning).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:52:14 +02:00
Julien HerrandClaude Sonnet 4.6 298765527c feat: add HTML view for individual email entries at /entries/:feedId/:receivedAt
Serves each email as a standalone HTML page with a Content-Security-Policy
header, useful for reading emails outside a feed reader and for debugging.
Also updates RSS item links to point to this route.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:51:28 +02:00
Julien HerrandClaude Sonnet 4.6 54e7a1bfa0 feat: parse <author> from From header in RSS items
Parse the From header into name + email parts so the feed library
renders proper RFC 2822 format (email (Name)) in <author> elements.
Also passes feedId to the generator so item links can point to the
upcoming /entries/:feedId/:receivedAt route.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:50:54 +02:00
Julien HerrandClaude Sonnet 4.6 6a6614fb26 docs: add TODO.md with feature gaps vs kill-the-newsletter
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:44:44 +02:00
Julien HerrandClaude Sonnet 4.6 da6494cbd8 ci: pin action versions to SHA + add github-actions to Dependabot
Dependabot will now open PRs when new versions of actions/checkout and
actions/setup-node are released, keeping the pinned SHAs up to date.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:34:59 +02:00
Julien HerrandClaude Sonnet 4.6 5694498d89 ci: publish dist/index.js to GitHub Release on version tag
Triggers on v* tags. Builds with wrangler --dry-run, falls back to
dist/worker.js and dist/_worker.js if index.js is not produced.
Uploads as index.js so homelab Tofu can fetch a stable URL.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:28:47 +02:00
Julien HerrandClaude Sonnet 4.6 50b1911854 chore: add Dependabot config for weekly npm updates
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:15:18 +02:00
Julien HerrandClaude Sonnet 4.6 243eec0cce refactor: extract ForwardEmail adapter to src/lib/forwardemail.ts
Mirrors the same pattern as cloudflare-email.ts — each provider has its
own adapter that translates provider-specific input into ProcessEmailInput
and delegates to processEmail(). inbound.ts is now a thin HTTP handler.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:13:43 +02:00
Julien HerrandClaude Sonnet 4.6 71a5c20e62 refactor: parallelize KV writes, cap metadata, remove redundant normalization
- email-processor: run email put + metadata get in parallel (saves one KV round-trip per email)
- email-processor: add missing 50-email metadata cap (was unbounded unlike storage.ts)
- email-processor: remove redundant normalizeEmail call on already-normalized allowedSender
- email-parser: strip WHAT-comments and boilerplate JSDoc throughout

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-20 23:05:26 +02:00
Julien HerrandClaude Sonnet 4.5 093efe7fc9 feat: add Cloudflare Email Workers support alongside ForwardEmail
Both email providers now work in parallel on the same Worker:
- ForwardEmail: existing POST /api/inbound webhook (unchanged)
- Cloudflare Email Routing: native `email` handler using postal-mime

New files:
- src/lib/email-processor.ts  shared business logic (feed lookup,
  sender allowlist, KV storage) extracted from inbound.ts
- src/lib/cloudflare-email.ts  Cloudflare `email` handler; parses
  raw RFC 2822 email with postal-mime, delegates to processEmail()
- src/lib/email-processor.test.ts  9 unit tests
- src/lib/cloudflare-email.test.ts  5 integration tests

Also fixes pre-existing CORS 204 response: c.text("", 204) →
c.body(null, 204) to match Hono's EmptyStatusCode constraint.

To enable: configure Cloudflare Email Routing with a catch-all rule
`*@domain.com` pointing to this Worker.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
2026-05-20 22:54:46 +02:00
Julien HerrandClaude Sonnet 4.5 29446a2aac chore: add typecheck script and fix pre-existing TypeScript errors
- Add `typecheck` script (`tsc --noEmit`) to package.json
- Remove conflicting `declare global` from test/setup.ts (superseded
  by @cloudflare/workers-types); use `globalThis as any` for test globals
  and declare minimal `require` locally to avoid pulling in @types/node
- Cast `createMockEnv()` and `deleteRes.json()` results in admin.test.ts
  to silence strict `unknown` / MockKV-vs-KVNamespace errors

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
2026-05-20 22:54:32 +02:00
Julien HerrandClaude Sonnet 4.5 3ed9d2ee22 chore: apply Prettier formatting to entire codebase
Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
2026-05-20 22:01:53 +02:00