Skip to content

Hand news and waitlist sign-ups to Utskick over a service binding - #38

Merged
frahlg merged 2 commits into
mainfrom
utskick-signups
Nov 4, 2026
Merged

frahlg merged 2 commits into
mainfrom
utskick-signups

Conversation

@frahlg

@frahlg frahlg commented Nov 4, 2026

Copy link
Copy Markdown
Member

What this does

  • /api/signup keeps every check it has today. It then calls env.UTSKICK.subscribe() instead of writing to the site's own D1 table. news and gateway become ftw-news and ftw-box in Utskick. The consent ids stay news-2026-10-30 and gateway-2026-10-30.
  • The site passes the visitor's IP address (CF-Connecting-IP) as ip, so Utskick limits sign-ups to 10 a minute per visitor, not for the whole site. Utskick keeps no IP address.
  • Without the binding, or when Utskick throws, the endpoint answers 503 and the forms say sign-up is closed.
  • signups.sql goes: Utskick owns the schema.
  • The privacy page, in English and Swedish, names Utskick and Amazon SES in Frankfurt, as it promised to before the first send.

This is docs/examples/ftw-web.patch from srcfl/utskick, and it applied cleanly. The ip field, its tests and the Swedish page are new here.

Before or right after merge

Merging to main deploys production through the Pages Git integration. Add a service binding to the Pages project ftw first, or right after:

Settings → Bindings → Service binding: UTSKICK → Worker utskick. Production only, never preview. A preview deployment with the binding would sign people up for real. Until the binding exists, the forms say sign-up is closed.

The site's D1 database ftw-signups may already hold sign-ups. Move them to Utskick through /admin/import with their original time and consent, then delete the site's copy and the SIGNUPS_DB binding, so each address lives in one place.

Tests

node --test tests/*.test.mjs: 44 pass, 0 fail. The sign-up tests fake the binding and check what the site sends, including ip and source: 'ftw-web', and the 503 when Utskick throws. Utskick tests its own deduplication. I checked the English privacy page in a browser and the Swedish page's markup.

For review

  • The privacy page says a click records "which link it was and when, and nothing about your device or IP address". Since Harden sending, add mail stats, open the MCP to all staff utskick#1, Utskick also stores a machine-or-not flag for each click. It judges that from the network and user agent, then drops both. The sentence may need a word on it.
  • The README says to change the consent ids and privacy.html#email together. This PR changes the text but keeps the ids, as planned.
  • Please read the Swedish text. I translated it from the English.
  • A visitor who hits the rate limit sees "Sign-up is closed right now", as for any other failure.

🤖 Generated with Claude Code

/api/signup keeps all its checks: same origin, JSON only, address,
list, country, the hidden bot field and one answer for new and known
addresses. It then calls env.UTSKICK.subscribe() instead of writing to
the site's own D1. UTSKICK is a service binding to the utskick Worker,
so the call stays inside Cloudflare. The lists news and gateway become
ftw-news and ftw-box in Utskick; the consent ids stay
news-2026-10-30 and gateway-2026-10-30. The site passes the visitor's
IP address from CF-Connecting-IP as ip, so Utskick limits sign-ups per
visitor, not for the whole site. Without the binding, or when Utskick
refuses, the endpoint answers 503 and the forms say sign-up is closed.

signups.sql goes: Utskick owns the schema. The tests fake the binding,
check what the site sends, and check the 503 when Utskick throws.

The privacy notice, in English and Swedish, now names the sender as it
promised: Utskick, delivering through Amazon SES in Frankfurt, a
one-way hash kept after someone leaves, no open tracking, and clicks
counted without device data or IP address.

Ported from srcfl/utskick docs/examples/ftw-web.patch, which applied
cleanly; the ip field, its tests and the Swedish page are new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-04T09:30:43.205753Z adadc16 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Deploying ftw with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3f7c45f
Status: ✅  Deploy successful!
Preview URL: https://c1966866.ftw-613.pages.dev
Branch Preview URL: https://utskick-signups.ftw-613.pages.dev

View logs

@frahlg
frahlg merged commit 1854e8f into main Nov 4, 2026
1 check passed
@frahlg
frahlg deleted the utskick-signups branch November 4, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant