Files
Daniel Avila 583e520a9b Add newsletter worker: weekly community components email via Resend (#698)
* Add newsletter worker: weekly community components email via Resend

New Cloudflare Worker (cloudflare-workers/newsletter/) that composes and
sends a weekly email featuring trending components (one Skill, Agent, MCP,
Hook and Setting per send). Selection is weighted-random by recent downloads
and all copy rotates from pools so no two emails read the same. Delivered as
a Resend Broadcast to the segment in RESEND_SEGMENT_ID, with per-recipient
unsubscribe handled by Resend, reply-to routing, and open/click tracking via
track.aitmpl.com. Cron: Sundays 16:00 UTC (slot freed by decommissioning the
claude-docs-monitor worker, documented in CLAUDE.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: escape URL in HTML, surface broadcast id, cron dedupe guard

- escapeHtml() on the component URL in the HTML href/text, consistent with
  every other dynamic field in the block
- Include the created broadcast id in the error when POST /broadcasts/{id}/send
  fails, so an orphaned broadcast can be recovered manually
- Guard the scheduled path against Cloudflare's occasional cron double-fire:
  pre-flight check for an existing non-draft "newsletter <today>" broadcast
  before sending (fails open; manual /trigger bypasses it so pilots can send
  multiple times a day)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: log non-2xx Sentry responses in newsletter sentry.js

reportError and checkIn now check response.ok and console.error rejected
envelopes (invalid DSN, auth failure, rate limiting) so observability
failures leave a signal in worker logs instead of passing silently. The
never-throws guarantee is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: close cron double-fire race with draft leader election

The read-then-create pre-check left a window where two overlapping cron
invocations could both pass the check and both send. The scheduled path now
runs a stateless leader election on Resend itself: each run creates its
broadcast as a draft, waits a 10s settle so concurrent drafts are visible,
lists today's broadcasts, and only the deterministic winner (smallest draft
id, and only if nobody already sent) calls /send; losers delete their draft
and yield. Chosen over Workers KV because KV has no compare-and-set and
cannot provide an atomic lock; a Durable Object would, but is overkill for
this worker. Manual /trigger sends still bypass dedupe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: prevent stale drafts from blocking same-day cron retries

Two guards against a transient /send failure leaving an abandoned draft that
would win every later election with nobody to send it:

- On /send failure the run now deletes its own draft before throwing, so a
  retry starts clean (if the send actually succeeded despite the error
  response, the broadcast is no longer a draft and the DELETE is a no-op).
- The election only yields to drafts created within the 5-minute overlap
  window; older drafts belong to runs that died before cleanup and are
  ignored as candidates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 22:01:33 -04:00
..

Newsletter — Weekly Community Components Email

Cloudflare Worker that composes and sends a simple weekly email featuring trending components (one Skill, Agent, MCP, Hook and Setting per send, in that fixed order) as a Resend Broadcast.

  • Rotating selection: each category pick is weighted-random by recent downloads — popular components appear more often, but every send differs.
  • Rotating copy: subjects, catalog intro, per-component sentences, stat phrasing and closers are drawn from pools, so no two emails read the same.
  • Minimal formatting: plain-text body plus a simple HTML version (bold + underlined component titles, clickable links, no styling beyond that).
  • Unsubscribe built-in: the body carries {{{RESEND_UNSUBSCRIBE_URL}}}; Resend replaces it per recipient, hosts the unsubscribe page, and skips unsubscribed contacts on future broadcasts automatically.
  • Replies go to NEWSLETTER_REPLY_TO.
  • Tracking: open/click tracking enabled on the aitmpl.com domain with tracking subdomain track.aitmpl.com. Per-broadcast metrics (delivered, opens, clicks per link) at resend.com/broadcasts.
  • Data sources: https://www.aitmpl.com/trending-data.json and /components.json (static dashboard assets).

Safety gate

The broadcast targets the segment in the RESEND_SEGMENT_ID secret. Point it at a small pilot segment for tests, or the full-audience segment for community-wide sends. Nothing outside that segment can receive the email.

Schedule

Cron: Sundays 16:00 UTC (0 16 * * SUN). Note: the Cloudflare account's free plan allows 5 cron triggers; this slot was freed by decommissioning the claude-docs-monitor worker (2026-07).

Endpoints

# Preview the generated email WITHOUT sending (hit repeatedly to see rotation)
curl "https://aitmpl-newsletter.<subdomain>.workers.dev/preview?format=text" \
  -H "Authorization: Bearer $TRIGGER_SECRET"

# Dry run (full runner, no send)
curl -X POST "https://aitmpl-newsletter.<subdomain>.workers.dev/trigger?send=false" \
  -H "Authorization: Bearer $TRIGGER_SECRET"

# Real send: creates + sends a Broadcast to RESEND_SEGMENT_ID
curl -X POST "https://aitmpl-newsletter.<subdomain>.workers.dev/trigger" \
  -H "Authorization: Bearer $TRIGGER_SECRET"

# Health
curl "https://aitmpl-newsletter.<subdomain>.workers.dev/status"

Development & Deploy

cd cloudflare-workers/newsletter
npm run dev          # Local dev (http://localhost:8787)
npx wrangler deploy  # Deploy

Configuration

Public vars live in wrangler.toml [vars]: DASHBOARD_URL, RESEND_FROM_EMAIL.

Secrets (via wrangler secret put <KEY>):

Secret Purpose
RESEND_API_KEY Resend full access API key (broadcasts + segments)
RESEND_SEGMENT_ID Segment the broadcast targets (pilot or full audience)
NEWSLETTER_REPLY_TO Reply-to address for the newsletter
TRIGGER_SECRET Auth for /trigger and /preview
SENTRY_DSN Optional — error reporting + cron check-ins (newsletter-weekly monitor slug)

Growing the audience

Contacts are loaded from Clerk (production instance) into Resend segments. The pilot batches were loaded with an ad-hoc script (Clerk API → resend contacts create + add-segment); the full-audience sync follows the same pattern over all users. Resend excludes unsubscribed/bounced contacts automatically.