Stu Mason
Stu Mason

Activity

StuMason/cleanconnect
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 existing ThrottlesResendApi concern (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-Post headers give Gmail/Outlook the native Unsubscribe control. CSRF-exempted the signed /unsubscribe POST 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} — queries marketing_opt_in users of the role; dry-run lists recipients; confirm before sending.
  • Registered users only. The 51 pre-launch signups are NOT touched — they keep their existing LaunchAnnouncementMail path (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