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.
142 lines
4.6 KiB
Markdown
142 lines
4.6 KiB
Markdown
# Send to E-Reader
|
|
|
|
Convert web articles to EPUB and email them straight to your e-reader. Works with any device that accepts emailed files — Kindle, PocketBook, or anything else with a similar service.
|
|
|
|
Built with Express 5 + TypeScript. Hosted at [read.atanasov.fi](https://read.atanasov.fi).
|
|
|
|
## How it works
|
|
|
|
Two ways to send something to your e-reader:
|
|
|
|
**URL → EPUB**
|
|
1. You paste an article URL and your e-reader's email address.
|
|
2. The server fetches the page, runs Mozilla Readability over it to strip ads/chrome, builds an EPUB, and emails it to the address you gave.
|
|
3. The EPUB is deleted from the server immediately after sending.
|
|
|
|
**Upload a file**
|
|
1. Drag (or pick) a file — EPUB, PDF, MOBI, anything your device accepts.
|
|
2. The server attaches it to an email and sends it to your e-reader as-is, no conversion.
|
|
3. The file is deleted from the server immediately after sending. Max size: 25 MB.
|
|
|
|
## Supported devices
|
|
|
|
The destination email domain is checked against an allowlist (default: `kindle.com`, `pbsync.com`) to prevent the app being used as an open email relay. Subdomains count, so `free.kindle.com` works too.
|
|
|
|
| Device | Email pattern |
|
|
|---|---|
|
|
| Amazon Kindle | `*@kindle.com` (or `*.kindle.com` subdomains) |
|
|
| PocketBook | `*@pbsync.com` |
|
|
|
|
You can extend the list via the `ALLOWED_READER_DOMAINS` env var (see below).
|
|
|
|
You'll also need to add the **sender** address to your device's approved-senders list — one-time per device.
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js 20 or newer
|
|
- An SMTP-capable email provider. Defaults are set up for [Resend](https://resend.com) — sign up (no waitlist), verify your domain, grab an API key.
|
|
|
|
## Local development
|
|
|
|
```bash
|
|
git clone <your-fork>
|
|
cd send_to_kindle
|
|
npm install
|
|
cp .env.example .env # then fill in SMTP_PASSWORD (Resend API key) and MAIL_FROM
|
|
npm run dev
|
|
```
|
|
|
|
Server listens on `http://localhost:3000` (override with `PORT`).
|
|
|
|
## Production build
|
|
|
|
```bash
|
|
npm run build
|
|
npm run start
|
|
```
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Required | Default | Notes |
|
|
|---|---|---|---|
|
|
| `SMTP_HOST` | no | `smtp.resend.com` | Any SMTP server works |
|
|
| `SMTP_PORT` | no | `587` | `587` = STARTTLS (preferred — port 465 is blocked by most VPS providers) |
|
|
| `SMTP_USER` | yes | — | For Resend, literally the string `resend` |
|
|
| `SMTP_PASSWORD` | yes | — | For Resend, your API key (`re_…`) |
|
|
| `MAIL_FROM` | yes | — | Sender address, e.g. `read@atanasov.fi`. Must be from a verified domain on your provider. |
|
|
| `ALLOWED_READER_DOMAINS` | no | `kindle.com,pbsync.com` | Comma-separated allowlist of destination email domains. Suffix-matching, so subdomains are allowed. |
|
|
| `PORT` | no | `3000` | Listen port |
|
|
|
|
## Resend setup (quick)
|
|
|
|
1. Sign up at [resend.com](https://resend.com).
|
|
2. Add your domain (e.g. `atanasov.fi`) and add the SPF/DKIM/return-path DNS records they generate. Verification usually completes in a few minutes.
|
|
3. Create an API key.
|
|
4. In `.env`:
|
|
```
|
|
SMTP_USER=resend
|
|
SMTP_PASSWORD=re_xxxxxxxxxxxxxxxxxx
|
|
MAIL_FROM=send@read.atanasov.fi
|
|
```
|
|
|
|
For a different provider, just point `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASSWORD` at it — any standard SMTP service works.
|
|
|
|
## API
|
|
|
|
### `POST /send-article`
|
|
|
|
Body (JSON):
|
|
```json
|
|
{
|
|
"url": "https://example.com/article",
|
|
"readerEmail": "your-device@kindle.com"
|
|
}
|
|
```
|
|
|
|
Response on success:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Article sent to your e-reader",
|
|
"title": "Article Title"
|
|
}
|
|
```
|
|
|
|
### `POST /send-file`
|
|
|
|
Body (multipart/form-data):
|
|
- `file` — the binary file (max 25 MB)
|
|
- `readerEmail` — destination address
|
|
|
|
Response on success:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "File sent to your e-reader",
|
|
"title": "original-filename.epub"
|
|
}
|
|
```
|
|
|
|
### Rate limit
|
|
|
|
Both endpoints share a limit of **20 sends per IP per hour**.
|
|
|
|
## Limitations
|
|
|
|
- Articles must be publicly accessible — no paywalled or login-required pages.
|
|
- Server-side fetch means JS-heavy SPAs may extract poorly (the page must render meaningful HTML without JavaScript).
|
|
- Some sites block non-browser user-agents; the app sends a Chrome UA but can still be refused.
|
|
|
|
## Stack
|
|
|
|
- [Express 5](https://expressjs.com/) — HTTP server
|
|
- [@mozilla/readability](https://github.com/mozilla/readability) — article extraction
|
|
- [jsdom](https://github.com/jsdom/jsdom) — DOM for Readability
|
|
- [@lesjoursfr/html-to-epub](https://github.com/lesjoursfr/html-to-epub) — EPUB generation
|
|
- [nodemailer](https://nodemailer.com/) — SMTP client
|
|
- [multer](https://github.com/expressjs/multer) — multipart/form-data upload parsing
|
|
- [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) — per-IP rate limiting
|
|
|
|
## License
|
|
|
|
GPL-3.0-only. See [LICENSE](LICENSE).
|