This Worker polls a Mailchimp RSS feed every 15 minutes and posts new items to a Discord webhook.
Behavior:
- Uses Cloudflare Workers with TypeScript
- Stores the last seen feed item in KV namespace
NEWSLETTER_STATE - First run does not post historical items
- Posts only newly detected items after that
- Sends Discord embeds with title, link, short description, and footer
Excelsior Running Club - Sanitizes Mailchimp HTML so Discord descriptions do not show CSS or raw markup
- Removes duplicated leading titles from the description when the newsletter body starts with the same heading
src/index.ts: Worker logictest/sanitize-description.test.ts: Regression test for description sanitization.github/workflows/ci.yml: GitHub Actions workflow that runs tests onmainpushes and pull requestswrangler.jsonc: Worker config, cron trigger, KV binding
- Node.js 22 recommended
- Install project dependencies:
npm installBefore running Cloudflare commands, authenticate with Wrangler:
npx wrangler loginCreate the production namespace:
npx wrangler kv namespace create NEWSLETTER_STATECreate the preview namespace:
npx wrangler kv namespace create NEWSLETTER_STATE --previewCopy the returned IDs into wrangler.jsonc.
Replace:
REPLACE_WITH_PRODUCTION_KV_NAMESPACE_IDREPLACE_WITH_PREVIEW_KV_NAMESPACE_ID
Set the Discord webhook URL:
npx wrangler secret put DISCORD_WEBHOOK_URLSet the Mailchimp RSS feed URL:
npx wrangler secret put MAILCHIMP_RSS_URLExample feed URL shape:
https://usX.campaign-archive.com/feed?u=...&id=...
Start remote dev with scheduled testing enabled:
npm run devTest the scheduled handler locally:
curl "http://127.0.0.1:8787/__scheduled?cron=*/15+*+*+*+*"Notes:
npm run devuseswrangler dev --remote --test-scheduled- This lets local testing use the secrets you set with
wrangler secret put - The first scheduled run only stores the latest feed item in KV
- No Discord message is sent on that first run
- Later runs post only items newer than the stored item
Run the sanitizer regression test locally:
npm testNotes:
- The test uses Node's built-in test runner
- The current test command uses
--experimental-strip-types, so Node 22 is the safest local and CI runtime - The fixture uses a sanitized Mailchimp-like sample to guard against CSS and duplicate-title regressions
Deploy the Worker:
npm run deployAfter deployment, Cloudflare will trigger the Worker every 15 minutes using:
*/15 * * * *
You need to provide:
- The two KV namespace IDs in
wrangler.jsonc - The
DISCORD_WEBHOOK_URLsecret - The
MAILCHIMP_RSS_URLsecret
- The Worker assumes the RSS feed is ordered newest-first, which is the normal Mailchimp RSS layout
- If the previously stored item is no longer present in the feed, the Worker advances the stored cursor to the current latest item and does not backfill older posts
- RSS parsing is done with small string-based parsing logic to avoid heavy dependencies
- The Discord description is limited to 240 characters and truncated with an ellipsis when needed
- The sanitizer strips HTML comments and
<style>,<script>, and<head>blocks before flattening the content to plain text
GitHub Actions runs npm test on:
- pushes to
main - all pull requests