Prevents the form being abused as an open email relay to arbitrary
addresses (which is the realistic abuse vector — disk fill, server
compromise, and device exploitation are all bounded already).
Default allowlist: kindle.com, pbsync.com. Suffix matching so
subdomains (e.g. free.kindle.com) are also accepted.
Configurable via ALLOWED_READER_DOMAINS env var so the list can be
extended without code changes.
Adds a second flow alongside the URL-to-EPUB pipeline:
- New POST /send-file endpoint accepts multipart/form-data with a `file`
and `readerEmail`, attaches the file to an email, and ships it to the
reader as-is. No conversion — the device decides what it accepts
(EPUB, PDF, MOBI, FB2, ...).
- 25 MB cap enforced both client-side and server-side (multer).
- Shares the same 20/hour rate limit as /send-article (now sendLimiter).
- UI: dropzone with drag/drop + click-to-pick + visible filename feedback.
- Shared e-reader email input across both flows.
- Codeberg source link added to the footer.
- Version bumped 0.1.0 → 0.2.0.
Hetzner (and most VPS providers) block outbound 465 by default as
anti-spam policy. Port 587 with STARTTLS is open and supported by
Resend (and any standard SMTP service). The transporter code already
flips `secure: false` automatically when port != 465, so no code
change is needed — just the default.
Prevents abuse before exposing the app publicly. Limits each client IP to
20 article sends per hour. trust proxy is set so the real client IP is
used (not Nginx's loopback) once the app is behind a reverse proxy.