Client SaaS
TypeScript
Pull Request Merged
PR #193 merged: feat: features announcement email to opted-in users (PR5)
The monthly "what's new" announcement to registered users who haven't opted out of marketing. Soft-opt-in basis: existing customers, our own service, opt-out in every email.
What
FeatureAnnouncementMail— role-switched (client vs cleaner copy), queued via the existingThrottlesResendApiconcern (stays under Resend's rate cap).- Reusable
mail.partials.footer— manage-preferences + signed unsubscribe links + the "we only send occasional updates, never booking/payment alerts" reassurance. - One-click unsubscribe (RFC 8058) —
List-Unsubscribe+List-Unsubscribe-Postheaders give Gmail/Outlook the native Unsubscribe control. CSRF-exempted the signed/unsubscribePOST so providers can one-click; the single signed URL serves both the GET confirm page and the one-click POST (same URI). email:send-feature-announcement {--role=client|cleaner} {--dry-run}— queriesmarketing_opt_inusers of the role; dry-run lists recipients; confirm before sending.
Scope / legal
- Registered users only. The 51 pre-launch signups are NOT touched — they keep their existing
LaunchAnnouncementMailpath (their consent is scoped to "notify me when you launch", which that one-off email satisfies). - Recovered the original coming-soon form copy to confirm that basis.
Tests
6 tests: role + opt-in filtering, dry-run sends nothing, invalid role rejected, mailable renders copy + preference links + one-click header, footer unsubscribe URL is validly signed, and a CSRF-less one-click POST actually unsubscribes. Existing unsubscribe suite still green.
Both emails rendered + eyeballed in a browser (logo, bullets, CTA, footer) — on-brand.
Note
Ships best after #192 (SMS) is live so the "text alerts" line is true. Copy is in scratch/pr5-announcement-prep.md — easy to tweak before sending.
+274
additions
-0
deletions
7
files changed