Skip to content

Get a daily email digest of an RSS feed

Instead of signing up for a feed-reader-to-email service, you can run the whole thing as one cron-triggered val: fetch the feed daily, diff against the last-run timestamp stored in blob storage, and email yourself the new items with std/email. If nothing new was published, no email is sent.

View and run this example on Val Town

import { blob } from "https://esm.town/v/std/blob/main.ts";
import { renderDigest } from "./digest.tsx";
import { sendWithRetry } from "./email.ts";
import { fetchNewItems } from "./rss.ts";
// Change this to the feed you want a daily digest of
const FEED_URL = "https://blog.val.town/rss.xml";
const LAST_RUN_KEY = "lastRunAt";
export default async function () {
const lastRunAt = (await blob.getJSON(LAST_RUN_KEY)) as string | undefined;
// First run: look back 24 hours
const since = lastRunAt
? new Date(lastRunAt)
: new Date(Date.now() - 24 * 60 * 60 * 1000);
const { feedTitle, items } = await fetchNewItems(FEED_URL, since);
console.log(`${items.length} new item(s) since ${since.toISOString()}`);
if (items.length > 0) {
await sendWithRetry({
subject: `${feedTitle}: ${items.length} new item${items.length === 1 ? "" : "s"}`,
html: renderDigest(feedTitle, items),
});
}
await blob.setJSON(LAST_RUN_KEY, new Date().toISOString());
}

The template splits the rest of the work into small files: rss.ts fetches and parses the feed (any RSS or Atom feed works), digest.tsx renders the email HTML, and email.ts wraps std/email with a small retry since sends can fail transiently.

  1. Remix this template — click the Remix button in the top right corner.
  2. In main.tsx, set FEED_URL to the feed you want.
  3. Optional: click Run on main.tsx to test it immediately. On the first run it looks back 24 hours.

No API keys or environment variables are needed.

  • The digest goes to your Val Town account email — that's where std/email delivers by default.
  • The schedule is 0 13 * * * — daily at 13:00 UTC (9am Eastern). Cron expressions always run in UTC, so adjust the schedule on main.tsx for your timezone.
  • The last-run timestamp in blob storage is what guarantees you never get the same item twice, even if you change the schedule.