Extract news links from a Gmail email, send them to a Cloudflare Worker for summarization, and render concise bullet points in-page via a Tampermonkey userscript. Summarization is adapter-based so different AI APIs can be swapped in; OpenAI is the default. The client sends all detected links; the Worker enforces how many get summarized.
- Tampermonkey client: injects a small UI, scrapes links from the open email body, and posts
{links, readingTime}to the backend. Displays headline (hyperlinked) and bullets returned. - Cloudflare Worker backend: validates requests, fetches each article, and calls a summarizer adapter. Deduplicates links and returns structured summaries.
- Summarizer adapters: interface-based;
OpenAISummarizeris included, others can be added behind the same contract.
- Install dependencies:
npm install - Configure env (Worker):
OPENAI_API_KEY: key for the OpenAI Chat Completions API.SUMMARIZER(optional):openai(default). Extendable later.- Local dev: put env in
.dev.vars; an example is in.env.example. - Optional
API_TOKEN: bearer token required on incoming requests for auth. Set viawrangler secret put API_TOKEN.
- Deploy the Worker via Wrangler (see “Deploy to Cloudflare” below); ensure it exposes
POST /summaries. - Configure the userscript (Tampermonkey):
- Install
src/client/gmail.jsdirectly (metadata kept). - In Gmail’s console (with the userscript installed), set values in Tampermonkey storage:
GM_setValue('BACKEND_URL', 'https://<your-worker>/summaries');GM_setValue('API_TOKEN', '<your-api-token>');(if you setAPI_TOKENon the Worker)
- Install
- Install Wrangler:
npm install -D wrangler(once per repo). wrangler.tomlis present:
name = "email-reader"
main = "src/worker.ts"
compatibility_date = "2024-11-17"
- Authenticate:
npx wrangler login(or setCLOUDFLARE_API_TOKEN). - Set secrets (at least your OpenAI key):
npx wrangler secret put OPENAI_API_KEY(andSUMMARIZERif you add adapters). - Local preview (Miniflare):
npx wrangler dev(POST tohttp://127.0.0.1:8787/summaries). Example curl:
curl -X POST http://127.0.0.1:8787/summaries \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_TOKEN>" \
-d '{"links":["https://www.staffingindustry.com/editorial/healthcare-staffing-report/venture-capital-investments-in-staffing-and-workforce-companies-reach-record-level"],"readingTime":"quick","articleLimit":1,"maxArticles":1}'
- Deploy:
npx wrangler deploy. The resulting URL is yourBACKEND_URL(append/summaries).
Notes:
- Wrangler bundles everything under
src/; you only ship the Worker entrypoint. - The Tampermonkey client is separate: install
src/client/gmail.jsdirectly in Tampermonkey (metadata header kept), setBACKEND_URLto the deployed Worker. - If
API_TOKENis set, requests must includeAuthorization: Bearer <API_TOKEN>. The userscript can setAPI_TOKENat the top ofsrc/client/gmail.jsto send this header.
- Add a failing test in
tests/for the unit you are extending (e.g., reading-time rules, adapter behavior, Worker validation). - Implement the minimal code to make it pass.
- Keep the Tampermonkey UI small and composable; prefer pure functions for link extraction to keep them testable.
- Run tests:
npm test(Vitest). For types:npm run typecheck.
- Input:
quick|default|long. - Mapping lives in
src/readingTime.tsand controls bullet count, token budget, and headline length.
src/worker.ts— Cloudflare Worker entrypoint; validates payloads and orchestrates summarization.src/articles.ts— fetch + text extraction + dedupe helpers.src/readingTime.ts— reading-time profiles and coercion.src/summarizer/— adapter factory and OpenAI adapter.src/client/gmail.js— UI/button injection for Gmail + backend call (plain JS, install directly).tests/— Vitest unit tests for the core logic and Worker validation.
- TypeScript, strict mode.
- No network calls in tests; mock
fetchand adapters. - Docs + changelog entries accompany meaningful code changes.
- Keep UI additions minimal and easy to remove; avoid mutating Gmail DOM outside the container you inject.
- Open an email in Gmail with links.
- Click the ✍️ icon in the Gmail top bar to open the side panel. Pick reading time (
quick,default,long). Press “Summarize”. - Backend returns headline + bullet list per link; links remain clickable. The header shows the count; timing is displayed above results.
- Implement
SummarizerAdapterinsrc/summarizer/<name>.ts. - Add adapter selection to
buildSummarizerinsrc/summarizer/index.ts. - Add tests covering adapter selection and failure modes.
- Worker tests mock both the summarizer factory and
fetchto avoid live calls. - Reading-time tests guard the profile mapping. Add more as policies evolve.
- Run
npm test. Typecheck:npm run typecheck.
- Client sends all detected links; Worker enforces
articleLimitandmaxArticles(passed from the client) to cap OpenAI calls and control spend. - Links are deduped and summarized in parallel; response order matches the input order.
- Headlines prefer the article page
<title>(cleaned of site suffixes like “| …”) before falling back to model output or URL. - Server logs include counts (received/deduped/summaries/skipped/OpenAI calls) and average adapter time per request.
- Add Chrome extension packaging and/or Gmail add-on manifest.
- Add article parsing improvements (readability extraction).
- Add rate limiting / debounce on the Worker to prevent abuse.
- Add integration tests with Miniflare.