一個收件箱。一個 PDF。新聞、全新填字遊戲、漢字練習與 Home Assistant 簡報,全部匯集於一版,並以電子郵件寄送至您的 Kindle Scribe、任何電子書閱讀器,或普通收件箱。
AI 翻譯
One inbox. One PDF. The news, a fresh write-in crossword, Kanji practice and your Home Assistant briefing, bound into one edition and emailed to your Kindle Scribe, any e-reader, or plain inbox every
媒體TypeScript · JavaScriptGitHub @RonanOD上線中營運者未確認
試用過了嗎? 點一下就好,不需要登入。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證提供 Kindle Scribe 及其他裝置的線上服務
AI 翻譯
Online Services for Kindle Scribe and other devices
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
dailyscribe.ca — pick from a catalogue of daily
"services" (the news, a write-in crossword, Kanji practice, a Home Assistant
briefing), and Daily Scribe binds them into one PDF and emails it to your Kindle
Scribe, any other e-reader, or a plain inbox — one edition a day, timed to your
morning. The Kindle Scribe is locked down by Amazon but accepts PDFs by email —
this turns that one open channel into a personalised daily edition. Some services
also read back: write your answers on the device by hand, mail the page to
[email protected], and it is graded automatically.檢視完整 README ↗dailyscribe.ca — pick from a catalogue of daily
"services" (the news, a write-in crossword, Kanji practice, a Home Assistant
briefing), and Daily Scribe binds them into one PDF and emails it to your Kindle
Scribe, any other e-reader, or a plain inbox — one edition a day, timed to your
morning. The Kindle Scribe is locked down by Amazon but accepts PDFs by email —
this turns that one open channel into a personalised daily edition. Some services
also read back: write your answers on the device by hand, mail the page to
[email protected], and it is graded automatically.
▶ Watch the 2-minute demo — a real edition opened on a Kindle Scribe: the news sections, the Home Assistant briefing, Kanji practice, and the write-in crossword.
Private beta, invite-only (ALLOW_NEW_SIGNUPS=false; the Auth.js signIn
callback rejects any address not already in users). The public site runs a
waitlist form; apps/web/scripts/approve-waitlist.mjs seeds approved emails into
users in small batches and sends the invite. See the roadmap in
CLAUDE.md.
GNU AGPLv3. The source is open — read it, self-host it, send patches. The AGPL's one added condition versus a permissive license: if you run a modified version of this code as a network service for others, you need to make your modified source available to them too.
flowchart TD
subgraph signup["Sign-up · invite-only"]
WL["Waitlist form<br/>(dailyscribe.ca)"] -->|"POST /api/waitlist"| WLDB[("waitlist")]
WLDB --> APV["approve-waitlist.mjs"]
APV --> USERS[("users")]
APV --> INV(["Resend invite"])
LOGIN["Sign in<br/>(my.dailyscribe.ca)"] -->|"Auth.js v5:<br/>Google · GitHub · magic-link"| USERS
LOGIN --> CFG[("subscriptions +<br/>userSecrets (AES-256-GCM)")]
end
subgraph delivery["Daily delivery"]
CRON["GitHub Actions<br/>every ~10 min<br/>(daily Vercel Cron fallback)"] -->|"GET /api/cron/dispatch"| DISP["dispatchDue()"]
CFG --> DISP
DISP --> RUN["runSubscription()<br/>timezone-aware · idempotent per day"]
RUN --> PLUG["ServicePlugin.run(ctx)"]
PLUG -->|"fetch"| SRC["RSS feeds · Home Assistant REST<br/>Kanji curriculum · Universal Crossword feed"]
PLUG -->|"render"| PDF["PDF (@react-pdf/renderer)"]
PDF --> ASM["Digest assembly<br/>(pdf-lib: branded cover + linked TOC)"]
ASM --> DLV["Deliverer / Resend"]
DLV --> AMZ["Amazon<br/>Approved Personal Document e-mail"]
AMZ --> DEV["Kindle Scribe /<br/>e-reader / inbox"]
end
subgraph back["Handwriting return path"]
DEV -->|"mail the page back"| RIN["POST /api/webhooks/resend-inbound"]
RIN -->|"pdfjs finds dailyscribe:service:token"| GEM["Gemini grading"]
GEM --> KP[("kanjiSubmissions +<br/>kanjiProgress")]
end
subgraph health["Delivery health"]
DLV -.->|"status webhook"| REV["POST /api/webhooks/resend-events"]
REV --> EVT[("deliveryEvents")]
REV -->|"hard bounce / complaint"| OFF["auto-disable subscription"]
end
classDef ext fill:#f4e9d6,stroke:#b0402e,color:#23201a;
class CRON,AMZ,GEM ext;
classDef store fill:#eef1f4,stroke:#5b6470,color:#23201a;
class WLDB,USERS,CFG,KP,EVT store;
Every [( … )] node is a collection in a single MongoDB Atlas database
(dailyscribe); the app has no other datastore. Cloudflare hosts DNS only —
DKIM/SPF/DMARC for the sending domain, the MX record that points inbound mail at
Resend, and the CNAMEs for both Vercel projects.
There is no job queue and no per-user cron. A GitHub Actions workflow
(.github/workflows/dispatch.yml) polls GET /api/cron/dispatch every ~10 minutes,
with a once-daily Vercel Cron as a fallback. Each subscription carries a delivery
time and an IANA timezone, and dispatchDue() sends a subscriber's edition on the
first poll at or past that local time — exactly once per day, enforced by a
deliveries marker row keyed by (userId, service, localDate). (Vercel Hobby cron
alone only fires daily, which can't honour a per-subscriber time across timezones;
native hourly Vercel Cron on Pro is the eventual replacement — see CLAUDE.md
Phase 3.)
pnpm + Turborepo. pnpm-workspace.yaml → apps/*, packages/*.
| Workspace | Package | What it owns |
|---|---|---|
apps/web | @dailyscribe/web | The dashboard, Auth.js, every API route, the cron dispatcher (lib/runner.ts), and all service plugins + PDF renderers (lib/plugins/*.tsx). Deploys to my.dailyscribe.ca. |
apps/marketing | @dailyscribe/marketing | The public site at dailyscribe.ca + www, the cross-origin waitlist form, and a git-based Decap CMS at /admin for the landing copy. No database, no auth. |
packages/core | @dailyscribe/core | Framework-free TS: Mongo client + collections() / ensureIndexes(), AES-256-GCM secret crypto, the ServicePlugin interface + registry, the Deliverer abstraction, pdf-lib merge/split, an SSRF guard, and the Gemini kanji-grading client. Ships raw .ts, consumed directly. |
packages/theme | @dailyscribe/theme | Shared design tokens (an oklch newsprint palette) and next/font config (Playfair Display + PT Serif), imported by both apps so product and marketing read as one brand. |
signIn gate.Daily Scribe <[email protected]>), one app-wide API key, no per-user
credentials. Handles outbound editions, magic links, invites, and both the
inbound-mail and delivery-status webhooks.GET /api/cron/dispatch (guarded
by CRON_SECRET) every ~10 min; a daily Vercel Cron is the fallback.@react-pdf/renderer for every rendered service; pdf-lib for stitching
a digest and drawing its cover/TOC links; pdfjs-serverless to read mailed-back
pages.@google/genai) — used only to grade a mailed-back Kanji
practice page./api/cron/dispatch every ~10
minutes (a daily Vercel Cron is the fallback). dispatchDue() loads every
enabled subscription and sends to each subscriber whose chosen delivery time has
already passed in their own timezone and whose edition hasn't gone out yet today;
it skips a user's standalone services if they have the Digest enabled.runSubscription() decrypts any needed secrets and calls
getPlugin(service).run(ctx). A plugin fetches its source (an RSS feed, the
user's Home Assistant REST API, the Kanji curriculum, the Universal Crossword
feed) and returns one or more PDF Assets. A successful send writes a
deliveries row; a failure writes one too, and never throws.collectDigestSections() runs each member service
with per-member failure isolation, then assembleDigestPdf() (pdf-lib) prepends
a branded cover + a linked table of contents and draws a back-to-contents link
on every page.createResendDeliverer().deliver() sends the PDF from the single
verified address. The user has whitelisted [email protected] once in Amazon's
Approved Personal Document E-mail List, so Amazon forwards it to the device./api/webhooks/resend-inbound. pdfjs-serverless scans the pages for the
embedded dailyscribe:<service>:<token> routing ref, trims the PDF to that
service's pages, and (for Kanji) sends it to Gemini for a per-character verdict
that updates kanjiProgress./api/webhooks/resend-events)
records deliveries, bounces, and complaints in deliveryEvents and
auto-disables any subscription whose address hard-bounced or filed a complaint;
the dashboard shows why.A service is a ServicePlugin — { id, label, run(ctx): Promise<Asset[]> }
(packages/core/src/plugins/index.ts). Plugins are registered once at the top of
apps/web/lib/runner.ts; their implementations live in
apps/web/lib/plugins/*.tsx. apps/web/lib/service-catalog.ts is the single source
of truth for a service's identity (label, icon, blurb, whether it needs a stored
secret, whether it appears in onboarding) and drives the dashboard tabs, the
onboarding picker, and the digest's member list.
Live: RTÉ / CBC / BBC news, Home Assistant summary, Kanji-a-day, the write-in (Universal) crossword, a solo DnD 5e campaign (mail-back moves, like Kanji), plus the Digest bundle. Coming: serialized classic novels, an eating/drinking tracker. Paused: the NYT crossword plugin still exists but is excluded from the catalogue (it needs the subscriber's own cookies).
pnpm install
pnpm dev # both apps via Turborepo
pnpm test # vitest in packages/core
pnpm typecheck
pnpm build
Requires Node ≥ 20 and pnpm 9. The apps need environment variables and a handful of
external accounts (MongoDB Atlas, Resend, OAuth apps, a Gemini key) to do anything
real — SETUP.md walks through every variable and the one-time
service setup, and finishes with an end-to-end "you as customer #1" checklist.