Add newsletter template#1064
Open
pfstr wants to merge 7 commits into
Open
Conversation
- Append a footer with unsubscribe link and SENDER_ADDRESS (postal address) to every campaign email - CAN-SPAM requires a physical address - Attach ready-made RFC 8058 List-Unsubscribe headers to every send - Unsubscribe links show a one-button confirmation page (GET) so mail scanners can't unsubscribe readers by accident; POST executes, including RFC 8058 one-click requests - Scrub name and extra fields on opt-out (data minimization); record confirmed_at / unsubscribed_at as a consent audit trail - Optional PRIVACY_URL renders a privacy-policy link under the signup forms; /admin warns while SENDER_ADDRESS is missing - README: Staying compliant section; e2e test for the confirmation step
- POST /api/send stores the campaign and queues one outbox row per subscriber, returns immediately; a minutely cron drains SEND_BATCH rows per run (default 40, sized for the free plan) - Atomic claims prevent double-sends, crashed runs are reclaimed after 10 minutes, up to 3 attempts per recipient - Unsubscribing cancels queued-but-undelivered emails to that address - Optional sendEmailBatch() hook delivers up to ~1,000 emails per API call; RSS auto-send queues through the same outbox - Migration 0003: outbox table, campaigns.body_html/base_url
…fails A throwing sendEmailBatch() previously failed the whole drain run and burned retry attempts without delivering anything. The drain now falls back to individual sendEmail() calls within the same run and logs delivery errors instead of swallowing them.
pfstr
force-pushed
the
add-newsletter-template
branch
from
July 23, 2026 12:12
1f7aa4f to
5255a5d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Newsletter template
A serverless newsletter that runs entirely on the user's own Cloudflare account: a hosted (and embeddable) signup form, compliant one-click unsubscribe (RFC 8058), and a simple
/adminpage to send a campaign. Subscribers live in D1, so the user owns the data.Sending is deliberately provider-agnostic:
src/email.tsis a smallsendEmail()adapter the user wires to their own email provider, so the template ships tied to no vendor. Collecting subscribers works out of the box with zero configuration.Queued sending
Campaigns go through a transactional outbox in D1:
POST /api/sendqueues one row per recipient and returns immediately; a minutely cron drainsSEND_BATCHrows per run (default 40 — sized so every invocation stays inside free-plan subrequest limits, whatever the list size). Claims are atomic (no double-sends across overlapping runs), crashed runs are reclaimed after 10 minutes, and each recipient gets up to 3 attempts. Unsubscribing also cancels that address's queued-but-undelivered emails. An optionalsendEmailBatch()hook in the adapter uses a provider's batch endpoint (one API call, many emails) for high-throughput delivery.Compliance built in
SENDER_ADDRESS) — the CAN-SPAM basicsList-Unsubscribe/List-Unsubscribe-Postheaders are built by the app and handed to thesendEmail()adapter on every sendOptional, off by default
DOUBLE_OPT_IN) — a confirmation-email step before a subscriber is addedSENDER_ADDRESS(postal address in every footer) andPRIVACY_URL(privacy-policy link under the signup form)Products & bindings
fetch(Hono) andscheduled(cron trigger)ADMIN_TOKEN,FROM_NAME,FROM_EMAIL,DOUBLE_OPT_IN,SEND_BATCHNotes for reviewers
playwright-tests/newsletter-template.spec.tsusing the shared fixtures.preview_image_url/preview_icon_urlleft out for the Growth team to provide; a screenshot is included in the README for now.