Ctrl K
ComunidadGuías…

docs/newsletter-subscriptions.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Confirmed newsletter subscriptions

The hosted newsletter uses a separate consent record on `growth_leads`.
Requesting updates never changes a demo disclosure, saved clinic preview, pause,
deletion, or platform email preference. Existing addresses captured only through
a notification are not subscribed automatically.

## Request and confirmation

The website posts to `/api/growth/newsletter` with an email, `consent: true`,
`disclosureVersion: "newsletter-confirmed-v1"`, an optional anonymous funnel UUID,
a coarse placement, and one interest: `general`, `offline`, `labs`, or `migration`.
The request promises three getting-started notes and occasional product updates.
Interest is self-reported context, not evidence that a clinic is ready to buy.

The endpoint validates allowed origins, body size, and per-IP/address limits.
New requests receive a signed 48-hour confirmation link. Retries within 15 minutes
reuse the token and provider idempotency key; a later request replaces the nonce.
Responses do not reveal whether an address exists, is paused, or is suppressed.

The link carries its token in a fragment. Opening the page does not confirm a
subscription. The reader must select **Confirm subscription**, which sends a POST.
The confirmation timestamp is immutable across retries. Confirmation links remain
usable when new signup requests are disabled. Expired or replaced links must be
requested again. The page does not need a staff session.

## Welcome sequence

`/api/cron/newsletter-followup` uses `CRON_SECRET`, the existing durable lead email
queue, stable provider idempotency keys, platform unsubscribe and suppression
checks, and cron heartbeats. It selects only valid confirmations after the
configured rollout timestamp. The three notes become eligible immediately,
after 48 hours, and after seven days, with at least 24 hours between actual
recorded sends. Unfinished introductions expire after 30 days. Delayed runs do
not send multiple steps at once.

A pause, deletion, or account created with the same address stops the introduction.
Suppressed introductions are retired from the queue without deleting subscription
history or re-enabling email. The sequence never restarts on a new signup request.
The interest-specific note describes current limitations; update that copy when
product availability changes. This cron sends only the three welcome notes;
ongoing product announcements require a separately reviewed campaign.

## Deployment and verification

Both switches default off. On self-hosted installations, the hosted billing guard
also disables these endpoints and sends.

1. Review and apply migration `0115_newsletter_subscription` through the normal
   migration workflow. Deploy the API, confirmation page, and cron.
2. Verify the configured email preference signing and identity keys against the
   existing registry, sender/reply address, provider, unsubscribe links, and cron
   heartbeat. Do not replace an identity key to make a health check pass.
3. With a dedicated test address, enable `NEWSLETTER_SIGNUP_ENABLED=true` and
   verify request, confirmation, retry, and unsubscribe. Test invalid and expired
   links. A GET must never confirm consent.
4. Deploy the website form with `NEXT_PUBLIC_NEWSLETTER_SIGNUP_ENABLED=true` only
   after the backend is ready. Keep the website and API disclosure versions equal.
   Retired `/api/subscribe` clients receive a refresh instruction, not a false
   subscription success.
5. Set a prospective UTC `NEWSLETTER_FOLLOWUP_ROLLOUT_AT`, then enable
   `NEWSLETTER_FOLLOWUP_ENABLED=true`. Verify controlled delivery before leaving
   the hourly schedule active. Monitor `newsletter-followup` heartbeats and queue
   outcomes. Disabling the follow-up switch stops future automated notes without
   altering consent or email preferences.

The optional local PostgreSQL integration tests run in randomly named disposable
databases, apply all migrations and RLS, and drop only those fixtures:

```sh
NEWSLETTER_DB_INTEGRATION=1 pnpm --filter @openpims/web exec vitest run \
  server/__tests__/newsletter.integration.test.ts \
  server/__tests__/newsletter-delivery.integration.test.ts
```

The delivery test substitutes the email transport; it proves queue, consent,
pacing, and suppression behavior without sending messages to real recipients.