Skip to content

Replace newsletter confirmation with a welcome email#8071

Open
gregory-boch-prisma wants to merge 3 commits into
mainfrom
feat/newsletter-welcome-auto-subscribe
Open

Replace newsletter confirmation with a welcome email#8071
gregory-boch-prisma wants to merge 3 commits into
mainfrom
feat/newsletter-welcome-auto-subscribe

Conversation

@gregory-boch-prisma

@gregory-boch-prisma gregory-boch-prisma commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What

  • subscribe website, blog, and docs newsletter forms immediately
  • send Brevo welcome template 228 only when a public contact enters list 15
  • preserve original SOURCE attribution and record the current path in NEWSLETTER_SOURCE
  • suppress duplicate welcomes and bound Brevo API work to 8 seconds
  • add a newsletter-only unsubscribe flow that preserves transactional email delivery
  • remove subscriber PII from recoverable error logs

Related Console source marker: prisma/pdp-control-plane#4560.

Brevo

  • template 228 is active and previewed with the new unsubscribe URL
  • NEWSLETTER_UNSUBSCRIBE_TOKEN and NEWSLETTER_SOURCE contact attributes are live
  • template 36 stays active until this PR is deployed, then it can be deactivated

Verification

  • 11 focused validation/subscription/unsubscribe tests
  • site, blog, and docs type checks
  • oxfmt and oxlint
  • desktop and 390 px mobile visual check

Rollout

After deployment, verify one new public subscription and one repeated subscription in Brevo, then deactivate legacy DOI template 36.

Summary by CodeRabbit

  • New Features
    • Newsletter signups now show immediate confirmation (“You’re subscribed…”) instead of prompting users to check their email.
    • Added a /newsletter/unsubscribe page with token validation and status feedback (success/invalid/error).
  • Bug Fixes
    • Standardized newsletter subscription API behavior and CORS handling across the blog, docs, and website.
  • Documentation
    • Updated newsletter API documentation for the immediate subscription flow and revised response examples.
  • Tests
    • Expanded tests for email validation, subscription/welcome handling, and unsubscription token behavior.

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Jul 15, 2026 4:23pm
docs Ready Ready Preview, Comment Jul 15, 2026 4:23pm
eclipse Ready Ready Preview, Comment Jul 15, 2026 4:23pm
site Ready Ready Preview, Comment Jul 15, 2026 4:23pm

Request Review

Comment thread packages/ui/src/lib/newsletter-route.ts Fixed
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

The newsletter implementation centralizes Brevo subscription and unsubscription logic, uses shared route handling across applications, adds a website unsubscribe flow, and updates documentation and success messages for immediate subscriptions.

Newsletter Flow

Layer / File(s) Summary
Shared subscription operations
packages/ui/src/lib/newsletter-subscription.ts, packages/ui/src/lib/newsletter-subscription.test.ts
Adds token-based Brevo subscription and unsubscription operations, welcome-email handling, structured errors, result types, and coverage for contact and list behaviors.
Shared newsletter API route
packages/ui/src/lib/newsletter-route.ts
Adds shared CORS, request validation, subscription responses, error handling, and method handlers.
Application route wiring and subscription messaging
apps/blog/..., apps/docs/..., apps/site/src/app/api/newsletter/route.ts, apps/docs/src/app/api/newsletter/README.md, apps/site/src/app/newsletter/newsletter-signup.tsx, packages/ui/src/components/newsletter.tsx
Configures shared routes for blog, docs, and website sources, documents immediate subscription behavior, and updates success messages.
Website unsubscribe flow
apps/site/src/app/api/newsletter/unsubscribe/route.ts, apps/site/src/app/newsletter/unsubscribe/page.tsx
Adds token validation, cookie redirects, Brevo list removal, error status handling, and the unsubscribe confirmation page.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: replacing confirmation-based newsletter flow with a welcome email.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@argos-ci

argos-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Jul 15, 2026, 4:29 PM

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/docs/src/app/api/newsletter/README.md (1)

84-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the newsletter API docs

apps/docs/src/app/api/newsletter/README.md:84-89,124-141 still has carry-over examples from the old handler: the 400 response should say "A valid email address is required", and the “Development Mode Debug Info” section should be removed since the shared route only returns a plain 500 error and no debug payload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/app/api/newsletter/README.md` around lines 84 - 89, Update the
newsletter API documentation examples in README.md: change the 400 response
error text to “A valid email address is required,” and remove the “Development
Mode Debug Info” section because the shared route returns only a plain 500
response without a debug payload.
🧹 Nitpick comments (4)
packages/ui/src/lib/newsletter-subscription.ts (2)

118-135: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Avoid reusing the Brevo API key as an HMAC secret.

createUnsubscribeToken signs with apiKey itself as the HMAC key. This works today (HMAC key-recovery is infeasible), but it couples an unsubscribe-token secret to your highest-privilege Brevo credential — anyone who can compute/verify tokens effectively needs the master API key, and you lose the ability to rotate one independently of the other. A dedicated NEWSLETTER_UNSUBSCRIBE_SECRET env var would keep blast radius smaller.

♻️ Suggested direction
-async function createUnsubscribeToken(apiKey: string, email: string): Promise<string> {
+async function createUnsubscribeToken(secret: string, email: string): Promise<string> {
   const key = await crypto.subtle.importKey(
     "raw",
-    new TextEncoder().encode(apiKey),
+    new TextEncoder().encode(secret),
     { name: "HMAC", hash: "SHA-256" },
     false,
     ["sign"],
   );

Since existing tokens are looked up by stored value (not recomputed), this change is backward compatible for already-issued tokens.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/lib/newsletter-subscription.ts` around lines 118 - 135,
Update createUnsubscribeToken to use a dedicated NEWSLETTER_UNSUBSCRIBE_SECRET
environment variable as the HMAC key instead of the Brevo apiKey. Validate that
the dedicated secret is configured before importing the key, while preserving
the existing token message and digest format.

137-153: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No timeout on outbound Brevo requests.

getContact, upsertContact, sendWelcomeEmail, and the unsubscribe lookup/update calls all rely on the bare fetch/fetcher with no AbortSignal.timeout(...). Since subscribeToPrismaNewsletter chains up to three sequential calls, a slow Brevo response leaves the request hanging until the hosting platform's own function timeout rather than failing fast with a clear error.

♻️ Suggested fix (repeat for each fetcher call)
-  const response = await fetcher(
-    `${BREVO_API_BASE_URL}/contacts/${encodeURIComponent(email)}?identifierType=email_id`,
-    { headers: getBrevoHeaders(apiKey) },
-  );
+  const response = await fetcher(
+    `${BREVO_API_BASE_URL}/contacts/${encodeURIComponent(email)}?identifierType=email_id`,
+    { headers: getBrevoHeaders(apiKey), signal: AbortSignal.timeout(5_000) },
+  );

Also applies to: 201-225

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/lib/newsletter-subscription.ts` around lines 137 - 153,
Introduce a shared timeout signal for outbound Brevo requests and pass it
through every fetcher call in getContact, upsertContact, sendWelcomeEmail, and
the unsubscribe lookup/update flow. Use AbortSignal.timeout with the established
request timeout value, ensuring sequential newsletter operations fail fast
instead of waiting for the platform timeout.
packages/ui/src/lib/newsletter-subscription.test.ts (1)

76-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a source-preservation assertion to this resubscribe test.

This test resubscribes via source: "blog" but never asserts what happens to attributes.SOURCE (it only checks the unsubscribe token is preserved). Given the PR's "preserve source ownership" goal, add a case where the existing contact has a different original SOURCE and assert it isn't overwritten — this would have caught the upsertContact gap flagged in newsletter-subscription.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/lib/newsletter-subscription.test.ts` around lines 76 - 102,
Extend the “keeps an existing unsubscribe token when a contact resubscribes”
test around subscribeToPrismaNewsletter by adding an existing contact
attributes.SOURCE value that differs from the requested "blog" source, then
assert the contact update body preserves that original source while retaining
the existing unsubscribe-token assertions.
apps/docs/src/app/api/newsletter/README.md (1)

17-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify list/template ID ownership and name the welcome template.

Step 1 tells the reader to "note your list ID," but step 3 says the shared helper owns the IDs — it's not obvious the "noting" step is purely a verification check against hardcoded values, not a config step. Also, no welcome template ID is named here, even though Troubleshooting (lines 120-121) explicitly calls out "incorrect/inactive welcome template ID" as a common failure — naming the expected template ID would make that section actionable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/app/api/newsletter/README.md` around lines 17 - 19, Clarify
step 1 to state that the list ID is being verified against the shared server
helper’s hardcoded value, not configured manually. Update step 2 to name the
expected newsletter welcome template ID and indicate it should be confirmed
active, keeping the ownership statement in step 3 consistent with these
verification-only steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/lib/newsletter-subscription.ts`:
- Around line 155-171: The upsertContact function overwrites an existing
contact’s SOURCE during resubscription. Update its attributes to reuse
contact?.attributes.SOURCE when present, otherwise fall back to the incoming
source; add a resubscribe test in
packages/ui/src/lib/newsletter-subscription.test.ts:76-102 using a different
source and assert the original SOURCE is preserved.

---

Outside diff comments:
In `@apps/docs/src/app/api/newsletter/README.md`:
- Around line 84-89: Update the newsletter API documentation examples in
README.md: change the 400 response error text to “A valid email address is
required,” and remove the “Development Mode Debug Info” section because the
shared route returns only a plain 500 response without a debug payload.

---

Nitpick comments:
In `@apps/docs/src/app/api/newsletter/README.md`:
- Around line 17-19: Clarify step 1 to state that the list ID is being verified
against the shared server helper’s hardcoded value, not configured manually.
Update step 2 to name the expected newsletter welcome template ID and indicate
it should be confirmed active, keeping the ownership statement in step 3
consistent with these verification-only steps.

In `@packages/ui/src/lib/newsletter-subscription.test.ts`:
- Around line 76-102: Extend the “keeps an existing unsubscribe token when a
contact resubscribes” test around subscribeToPrismaNewsletter by adding an
existing contact attributes.SOURCE value that differs from the requested "blog"
source, then assert the contact update body preserves that original source while
retaining the existing unsubscribe-token assertions.

In `@packages/ui/src/lib/newsletter-subscription.ts`:
- Around line 118-135: Update createUnsubscribeToken to use a dedicated
NEWSLETTER_UNSUBSCRIBE_SECRET environment variable as the HMAC key instead of
the Brevo apiKey. Validate that the dedicated secret is configured before
importing the key, while preserving the existing token message and digest
format.
- Around line 137-153: Introduce a shared timeout signal for outbound Brevo
requests and pass it through every fetcher call in getContact, upsertContact,
sendWelcomeEmail, and the unsubscribe lookup/update flow. Use
AbortSignal.timeout with the established request timeout value, ensuring
sequential newsletter operations fail fast instead of waiting for the platform
timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 865b1501-f417-4aab-ac1d-abbb6aad44cc

📥 Commits

Reviewing files that changed from the base of the PR and between 9695887 and 6c02137.

📒 Files selected for processing (11)
  • apps/blog/src/app/api/newsletter/route.ts
  • apps/docs/src/app/api/newsletter/README.md
  • apps/docs/src/app/api/newsletter/route.ts
  • apps/site/src/app/api/newsletter/route.ts
  • apps/site/src/app/api/newsletter/unsubscribe/route.ts
  • apps/site/src/app/newsletter/newsletter-signup.tsx
  • apps/site/src/app/newsletter/unsubscribe/page.tsx
  • packages/ui/src/components/newsletter.tsx
  • packages/ui/src/lib/newsletter-route.ts
  • packages/ui/src/lib/newsletter-subscription.test.ts
  • packages/ui/src/lib/newsletter-subscription.ts

Comment thread packages/ui/src/lib/newsletter-subscription.ts
@gregory-boch-prisma

Copy link
Copy Markdown
Contributor Author

Argos note: the single changed snapshot is unrelated to this PR's newsletter UI. Both reruns point to apps/eclipse/tests/example.spec.ts (chromium/homepage.png). The baseline captured the Docs introduction page at 1280×1316, while the branch run captured the Eclipse design-system page at 1280×9360. Argos reports this snapshot at 53% stability / 10% consistency (42 changes across 89 builds).

I did not approve the visual change. It needs an Argos reviewer to mark the cross-app capture as flaky/unrelated: https://app.argos-ci.com/developer-connections/web/builds/7932

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.

2 participants