惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
Vercel News
Vercel News
Recent Announcements
Recent Announcements
B
Blog RSS Feed
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
MongoDB | Blog
MongoDB | Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
雷峰网
雷峰网
D
Docker
Jina AI
Jina AI
IT之家
IT之家
人人都是产品经理
人人都是产品经理
L
LangChain Blog
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
MyScale Blog
MyScale Blog
博客园 - 叶小钗
The GitHub Blog
The GitHub Blog
The Cloudflare Blog
A
About on SuperTechFans
Hugging Face - Blog
Hugging Face - Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Chat to build and schedule your own personal Hacker News ...
Anmol Baranwal · 2026-05-16 · via DEV Community

I have been working on a side project for the past few weeks and finally got it polished enough to share.

Hacker News is one of my favorite places to keep up with tech and come across good discussions. But there's so much happening there that I kept missing the posts I actually cared about.

Even checking it twice a day, the front page rarely lines up with my interests. Tried a few existing Hacker News newsletters, but they were too generic -- everyone gets the same top stories.

So I figured, why not let myself and others build their own email digest from live data and schedule it whenever they want? That's why I built HN Digest.

You can chat to build your own personalized Hacker News newsletter, preview it live with real HN data, and schedule it to your inbox -- daily, weekly, or monthly, 70+ timezones, no password, all free.

Here is the GitHub Repository and you can try it live at hndigest.vercel.app.

Here's what it does and how I built it, along with the architecture.


What it does

You just have to describe whatever you want in the chat editor: security news, ask HN, trending posts, discussions, show hn - it adds them to your newsletter.

hndigest

There are 10 section types with per-section filters for keyword, timeframe (24h to 30d), minimum upvotes and story count.

So you can say "security news from the last 48 hours plus recent Show HN" -- it picks the right sections and applies the filters. Preview is a live React Email render so what you see is the actual email.

prompt

You can also just ask the chat:

  • to show all the sections available
  • delete or reorder
  • change the theme (light/dark) of the newsletter

Clicking a story's title takes you to the original site. Clicking comments takes you to the HN post.

Once you are happy, just activate using a magic link. You will need a Resend API key -- their free tier is 3000 emails/month, you will use about 30 even if you send daily.

Sends go through your Resend key (and your quota, not mine). You can pause or delete anytime from the dashboard.

Yes, it lands in my inbox every day but I often forget about it. I definitely need to change the time to evening! 🤣

dashboard

I have included a self-host guide in the readme docs as well.

Here is the stack:

Fun fact: I have tried different ways to solve this problem before (web scraping, GitHub Actions) & I believe this is the most practical solution out of all the rest.

different ways to solve hndigest


How it works

Here's the high-level flow.

Browser (editor + chat + live preview)
 ├→ /api/copilotkit  → LLM returns tool calls
 ├→ /api/preview     → React Email render → iframe
 └→ /api/activate    → encrypt key → Neon → create QStash schedule
                                           ↓ (HTTP cron callback)
                                    /api/send → Neon (fetch schedule)
                                             → User's Resend (send)

The chat doesn't directly write to the database. The LLM's tool calls only update the browser state. Nothing gets persisted to Neon until you hit "Activate". That's the only path that writes to the DB and creates a QStash schedule.

QStash is the only thing that triggers /api/send. The endpoint validates a CRON_SECRET header on every request, magic links and sessions are signed JWTs, and the Resend key stays AES-256-GCM encrypted at rest. Nothing sensitive is reachable from the browser.

I have added dedicated architecture docs to the repo, if you want to explore yourself.

The editor and live preview

Every change in the editor is a typed function call, not a blob of JSON. CopilotKit's useFrontendTool hook takes a zod schema and a handler -- the LLM chooses a tool, fills the params and the handler enforces what's allowed.

useFrontendTool({
  name: "add_section",
  parameters: z.object({
    type: z.enum(["hn-stories", "topic", "recent-gems", "high-signal", ...]),
    count: z.number().min(1).max(30).optional(),
    query: z.string().optional(),
    hours: z.number().optional(),
    minPoints: z.number().optional(),
  }),
  handler: async ({ type, count, query, hours, minPoints }) => {
    // Block duplicates for singleton types, strip invalid params, etc.
    const section = {
      id: `${type}-${Date.now()}`,
      type,
      props: { count, query, hours, minPoints },
    };
    setConfig(prev => ({ ...prev, sections: [...prev.sections, section] }));
    return `Added ${type} section`;
  },
});

The LLM never writes HTML or emits JSON that I have to validate. Guardrails live in the handler -- no duplicate singleton sections, and standard sections silently strip hours/minPoints they don't support.

Every config change hits /api/preview, which renders the React Email template server-side and returns HTML. The editor caches by config hash so repeated edits don't re-fetch.

const cacheKey = `${PREVIEW_CACHE_VERSION}:${JSON.stringify(config)}`;
const cached = previewCacheRef.current.get(cacheKey);
if (cached) { setPreviewHtml(cached); return; }

const { html } = await (await fetch("/api/preview", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ config }),
})).json();

previewCacheRef.current.set(cacheKey, html);
setPreviewHtml(html);

Activation and key encryption

You enter a Resend API key, add a recipient, and hit activate. The key gets encrypted with AES-256-GCM before it ever touches the database. It's decrypted only in memory at send time, never logged, never returned to the browser.

export function encrypt(text: string): string {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv("aes-256-gcm", getKey(), iv);
  let enc = cipher.update(text, "utf8", "hex") + cipher.final("hex");
  return `${iv.toString("hex")}:${cipher.getAuthTag().toString("hex")}:${enc}`;
}

A magic link (signed JWT, 30-minute expiry) is sent through your Resend key. Click it and you're in your dashboard.

Per-user scheduling with QStash

I first tried this with Vercel Hobby cron but it runs once a day for the whole project, which doesn't work when each user wants their own time and timezone. QStash lets every activation create its own schedule so I ended up using this.

const cron = toCron(frequency, time, day);  // "0 9 * * 1" for Mon 9am
await client.schedules.create({
  destination: `${baseUrl}/api/send?id=${scheduleId}`,
  cron,
  headers: { "x-cron-secret": process.env.CRON_SECRET },
});

Timezone conversion happens client-side -- pick Asia/Tokyo 8am, the editor converts to 23:00 UTC, that's what gets stored and scheduled.

QStash

The send endpoint

On cron, it runs: decrypt → fetch HN → render → send. Protected by a shared header secret so only QStash can hit it.

// /api/send
if (req.headers.get("x-cron-secret") !== process.env.CRON_SECRET) return 401;

const record = await getScheduleById(id);
const data = await fetchNewsletterData(record);
const html = await render(<NewsletterEmail config={record} data={data} ... />);

const resend = new Resend(decrypt(record.encryptedResendKey));
await resend.emails.send({
  from: "HN Digest <onboarding@resend.dev>",
  to: record.recipients,
  subject,
  html,
});

Every send fetches fresh Hacker News data. No pre-rendering, no staleness. Sends go through your Resend key (and your quota, not mine), with a one-click unsubscribe that cancels the schedule. You can also pause it from the dashboard.

complete flow

The pattern itself transfers easily. Swap the Hacker News API for Product Hunt, GitHub trending, Reddit, or any RSS feed, and you've got a personalized digest builder for that source. Same encryption, same chat editor, same per-user schedule.


This one was fun to build. A couple of my friends and I have been using it for the past month.

Would really appreciate some honest feedback :)

Try it here: hndigest.vercel.app
GitHub: github.com/Anmol-Baranwal/hndigest

Connect with me on GitHub, Twitter and LinkedIn.

thanks for reading!